/* ** 2026-02-04 ** ** The author disclaims copyright to this source code. In place of ** a legal notice, here is a blessing: ** ** May you do good and not evil. ** May you find forgiveness for yourself and forgive others. ** May you share freely, never taking more than you give. ** ************************************************************************* ** ** This file contains the SQLite vector ANN extension "vec1". To build ** as a loadable module on x86_64 with gcc: ** ** gcc -O3 -DNDEBUG -mavx2 -mfma vec1.c -shared -fPIC -o vec1.so ** ** Documentation available at: ** ** https://sqlite.org/vec1 */ /* ** The two distance metrics supported by the system. */ #define VEC1_DISTANCE_L2 1 #define VEC1_DISTANCE_COS 2 /* ** Default values for training parameters. */ #define VEC1_TRAINING_DEFAULT_CODESIZE 0 #define VEC1_TRAINING_DEFAULT_NBUCKET 0 #define VEC1_TRAINING_DEFAULT_SVD_VERIFY 0 #define VEC1_TRAINING_DEFAULT_OPQ 0 #define VEC1_TRAINING_DEFAULT_NOPQ_ROUND 5 #define VEC1_TRAINING_DEFAULT_RESIDUAL 1 #define VEC1_TRAINING_DEFAULT_DISTANCE VEC1_DISTANCE_L2 /* ** Max and min values for various parameters. ** ** VEC1_MAX_K: ** At present, a KANN query with an index allocates K*16 bytes for ** the heap data structure used to accumulate the K best matches. ** In its default configuration, sqlite3_malloc() is limited to ** 1GB allocations. So limit K so as to at least avoid SQLITE_NOMEM ** errors in the default configuration. */ #define VEC1_MAX_K 32*1024*1024 /* ** Software version. Returned by "SELECT vec1_info()". */ #define VEC1_VERSION "0.8" /* ** Values for Vec1ModelHeader.iVersion: ** ** VEC1_HEADER_VERSION: ** Used by default, if nothing below applies. ** ** VEC1_HEADER_VERSION_BIN: ** Used if the model uses a binary-quantizer. */ #define VEC1_HEADER_VERSION (0*1000 + 4) #define VEC1_HEADER_VERSION_BIN (0*1000 + 7) /* ** Shadow tables version. Stored in %_config table. Corresponds to the ** minimum version that matches the current shadow table setup. */ #define VEC1_CURRENT_FMTVERSION (0*1000 + 4) #include "sqlite3.h" /* ** Unless VEC1_STATIC is defined, compile as a loadable extension. */ #ifndef VEC1_STATIC # include "sqlite3ext.h" SQLITE_EXTENSION_INIT3 #endif /* System include files */ # include # include # include # include # include # include /* Standard sized types. */ typedef sqlite3_int64 i64; typedef sqlite3_uint64 u64; typedef unsigned char u8; typedef unsigned int u32; typedef unsigned short u16; typedef float f32; typedef int i32; typedef char i8; #if !defined(VEC1_TEST_SCALAR) #define VERTICAL_SUM_GENERIC(aAcc, nAcc, I) { \ int dd; \ for(dd=4; dd1 ) aAcc[0] = I (aAcc[0], aAcc[1]); \ } #ifdef __AVX2__ # define VEC1_HAVE_AVX2 1 # define VEC1_TRANSPOSE_WIDTH 32 # define VEC1_NTRANSPOSE_WIDTH 8 /* ** Dimensions used for vec1BestMatchN() and vec1BestMatchTransposeN(). */ # define VEC1_MULTIMATCH_NVEC 8 # define VEC1_MULTIMATCH_NCENT 8 /* AVX2 builds should use transposed arrays of centroids where possible. ** This avoids horizontal sums (expensive by design on AVX2) and frees ** up registers, allowing more concurrent FMA and other operations. */ # define VEC1_USE_TRANSPOSITION 1 /* ** When assembling the covariance matrix (part of OPQ rotation training), ** deal with (VEC1_COVAR_MULTIMATCH_MULT * VEC1_MULTIMATCH_NVEC) vectors ** at a time. See vec1CovarianceWork() for details. */ # define VEC1_COVAR_MULTIMATCH_MULT 1 # include # if defined(__FMA__) || defined(_MSC_VER) /* With MSVC, __AVX2__ implies __FMA__ */ # define FMADD(a,b,c) _mm256_fmadd_ps((a),(b),(c)) # define FMADD64(a,b,c) _mm256_fmadd_pd((a),(b),(c)) # else # define FMADD(a,b,c) _mm256_add_ps((c), _mm256_mul_ps((a),(b))) # define FMADD64(a,b,c) _mm256_add_pd((c), _mm256_mul_pd((a),(b))) # endif /* Load 8 unaligned floats. Or 4 unaligned doubles. */ # define LOADU(a) _mm256_loadu_ps(a) # define LOADU64(a) _mm256_loadu_pd(a) /* ** Parameter vec is a __m256 register containing 8 floats. Parameter ** s is a float. This macro sets s to the sum of the 8 values in vec. i.e. ** ** s = vec[0] + vec[1] + vec[2] + vec[3] ** + vec[4] + vec[5] + vec[6] + vec[7]; */ # define HORIZONTAL_SUM(s, vec) \ { \ __m128 vc_high = _mm256_extractf128_ps(vec, 1); \ __m128 vc_low = _mm256_castps256_ps128(vec); \ __m128 vc128 = _mm_add_ps(vc_high, vc_low); \ vc128 = _mm_hadd_ps(vc128, vc128); \ vc128 = _mm_hadd_ps(vc128, vc128); \ s = _mm_cvtss_f32(vc128); \ } /* ** Parameter vec is a __m256 register containing 4 doubles. Parameter ** s is a double. This macro sets s to the sum of the 4 values in vec. i.e. ** ** s = vec[0] + vec[1] + vec[2] + vec[3] */ # define HORIZONTAL_SUM64(s, vec) \ { \ __m128d _lo = _mm256_castpd256_pd128(vec); \ __m128d _hi = _mm256_extractf128_pd(vec, 1); \ __m128d _sum = _mm_add_pd(_lo, _hi); \ (s) = _mm_cvtsd_f64(_mm_hadd_pd(_sum, _sum)); \ } /* ** aAcc[] is an nAcc element array of type _m256 containing 8 32-bit floats. ** This macro sums the vectors and writes the results to aAcc[0]. */ # define VERTICAL_SUM(aAcc, nAcc) VERTICAL_SUM_GENERIC(aAcc,nAcc,_mm256_add_ps) #elif defined(__aarch64__) # define VEC1_HAVE_NEON 1 # define VEC1_TRANSPOSE_WIDTH 32 # define VEC1_MULTIMATCH_NVEC 4 # define VEC1_MULTIMATCH_NCENT 4 # define VEC1_COVAR_MULTIMATCH_MULT 4 # include /* ** aAcc[] is an nAcc element array of type float32x4. This macro sums the ** vectors and writes the results to aAcc[0]. */ #define VERTICAL_SUM(aAcc, nAcc) VERTICAL_SUM_GENERIC(aAcc, nAcc, vaddq_f32) #endif #endif /* VEC1_TEST_SCALAR */ #ifndef VEC1_TRANSPOSE_WIDTH /* Scalar fallback. Even scalar needs transpose widths. */ # define VEC1_TRANSPOSE_WIDTH 8 # define VEC1_MULTIMATCH_NVEC 4 # define VEC1_MULTIMATCH_NCENT 4 # define VEC1_COVAR_MULTIMATCH_MULT 1 #endif #define VEC1_LARGEST_INT64 (0xffffffff|(((i64)0x7fffffff)<<32)) #define VEC1_SMALLEST_INT64 (((i64)-1) - VEC1_LARGEST_INT64) #define SWAP(T, a, b) { T tmp; tmp = a; a = b; b = tmp; } /* ** Return a hardware counter value. These are used to calculate the ** percentages in the performance reports from vec1cat.qprofile and the ** final progress callback from within vec1_train(). */ static u64 vec1HardwareTimer(void){ u64 ret = 0; #if !defined(__STRICT_ANSI__) # if defined(__aarch64__) __asm__ __volatile__("mrs %0, cntvct_el0" : "=r"(ret)); # elif defined(__GNUC__) && defined(__x86_64__) unsigned int lo, hi; __asm__ __volatile__("rdtscp" : "=a"(lo), "=d"(hi) :: "%rcx"); ret = ((u64)hi << 32) | lo; # endif #endif return ret; } /* ** Figure out which threading model to use. Define VEC1_THREADS_WINDOWS ** to use windows threads, VEC1_THREADS_PTHREADS to use pthreads, or ** neither to use no threads. */ #if !defined(VEC1_THREADS) || VEC1_THREADS!=0 # if defined(_WIN32) # define VEC1_THREADS_WINDOWS 1 # define VEC1_THREADS 1 # elif defined(__unix__) || defined(__APPLE__) || defined(__linux__) # define VEC1_THREADS_PTHREADS # define VEC1_THREADS 1 # else # define VEC1_THREADS 0 # endif #endif #define size_of_array(X) ((int)(sizeof(X)/sizeof((X)[0]))) #define MAX(a,b) ((ab) ? (b) : (a)) #define UNUSED_PARAMETER(x) (void)(x) #define UNUSED_PARAMETER2(x,y) (void)(x),(void)(y) typedef struct Vec1Tab Vec1Tab; typedef struct Vec1TabList Vec1TabList; /* ** An instance of the following object is allocated when registering ** the vec1 module with a database handle. A pointer is passed as the ** the context pointer to sqlite3_create_module_v2(). It maintains a ** list of all Vec1Tab tables attached to the database handle. ** ** This object does not need a mutex, as it belongs to a single db handle. ** The db handle mutex ensures exclusive access. */ struct Vec1TabList { int nRef; sqlite3 *db; Vec1Tab *pFirst; int nThread; /* Number of threads to use for various ops */ double nProbeArg; /* Default "nprobe" for queries */ }; /* ** This structure represents a model-header. When serialized, the format ** is a packed array of six 32-bit big-endian integers, in the order ** shown in the struct below. */ typedef struct Vec1ModelHeader Vec1ModelHeader; struct Vec1ModelHeader { u32 iVersion; /* Version number currently 0004 */ u32 flags; u32 nElem; /* Size of vectors in elements */ u32 nCodebook; /* Number of codebooks for PQ */ u32 nBucket; /* Number of buckets for IVF */ u32 eDistance; /* A VEC1_DISTANCE_XXX value */ }; #define VEC1_HEADER_SIZE (6*sizeof_u32) /* ** Candidate values for the "flags" field of a Vec1ModelHeader. As follows: ** ** VEC1_MODEL_INDEX: ** Set for any configuration except "none". ** ** VEC1_MODEL_ROTATE: ** Set if model includes a full rotation matrix. ** ** VEC1_MODEL_RESIDUAL: ** Set if residual, not full, vectors are encoded. ** ** VEC1_MODEL_BITQUANT: ** Set to use bitwise quantization instead of PQ. Compressed vectors are ** a 32-bit float (the norm) followed by bitwise sketch of the vector ** or residual vector. ** ** VEC1_MODEL_WHT: ** Do Walsh Hadamard transform. */ #define VEC1_MODEL_INDEX 0x01 #define VEC1_MODEL_ROTATE 0x02 #define VEC1_MODEL_RESIDUAL 0x04 #define VEC1_MODEL_BITQUANT 0x08 #define VEC1_MODEL_WHT 0x10 /* ** Use these instead of actual sizeof() macros to avoid signed/unsigned ** compiler warnings. */ #define sizeof_u16 2 #define sizeof_u32 4 #define sizeof_f32 4 #define sizeof_f64 8 /* ** The three types of vector elements (that will eventually be) supported. */ #define VEC1_TYPE_FLOAT32 0 #define VEC1_TYPE_INT32 1 #define VEC1_TYPE_INT8 2 /* ** Maximum allowed threads for both vec1 and vec1_train(). */ #define VEC1_MAX_NTHREAD 128 static int vec1CorruptError(){ return SQLITE_CORRUPT_VTAB; } #define VEC1_CORRUPT vec1CorruptError(); static void vec1PutU32(u8 *aBuf, u32 val){ aBuf[0] = (val >> 24) & 0xFF; aBuf[1] = (val >> 16) & 0xFF; aBuf[2] = (val >> 8) & 0xFF; aBuf[3] = (val >> 0) & 0xFF; } /* ** Write a model header into buffer aBuf[]. */ static void vec1HeaderWrite( u8 *aBuf, int flags, int nElem, int nCodebook, int nBucket, int eDistance ){ /* VEC1_HEADER_VERSION_BIN if the model uses a binary quantizer, ** VEC1_HEADER_VERSION otherwise. */ u32 eHdrVersion = VEC1_HEADER_VERSION; if( flags & VEC1_MODEL_BITQUANT ){ eHdrVersion = VEC1_HEADER_VERSION_BIN; } vec1PutU32(&aBuf[0], eHdrVersion); vec1PutU32(&aBuf[4], flags); vec1PutU32(&aBuf[8], nElem); vec1PutU32(&aBuf[12], nCodebook); vec1PutU32(&aBuf[16], nBucket); vec1PutU32(&aBuf[20], eDistance); } static u32 vec1GetU32(const u8 *aBuf){ return (((u32)aBuf[0]) << 24) + (((u32)aBuf[1]) << 16) + (((u32)aBuf[2]) << 8) + (((u32)aBuf[3]) << 0); } /* ** Deserialize buffer aBuf into object p. */ static void vec1HeaderRead(const u8 *aBuf, Vec1ModelHeader *p){ p->iVersion = vec1GetU32(&aBuf[0]); p->flags = vec1GetU32(&aBuf[4]); p->nElem = vec1GetU32(&aBuf[8]); p->nCodebook = vec1GetU32(&aBuf[12]); p->nBucket = vec1GetU32(&aBuf[16]); p->eDistance = vec1GetU32(&aBuf[20]); } /* ** Version of sqlite3_free() that can be used as a destructor function with ** things like sqlite3_bind_blob(). */ static void vec1SqliteFree(void *x){ sqlite3_free(x); } typedef struct Vec1AnnResult Vec1AnnResult; struct Vec1AnnResult { sqlite3_int64 iRowid; double fDist; }; typedef struct Vec1AnnHeap Vec1AnnHeap; struct Vec1AnnHeap { Vec1AnnResult *aRes; /* Heap array */ i64 nRes; /* Current number of elements */ i64 nMax; /* Capacity */ i64 nAlloc; /* Current allocated size of aRes[] */ int rc; /* Error code (perhaps SQLITE_NOMEM) */ int bStreaming; double fMin; /* Current threshold for admittance */ }; typedef struct Vec1Buffer Vec1Buffer; struct Vec1Buffer { unsigned char *a; int n; int nAlloc; }; /* ** Return the dot-product of vectors A and B, each d elements in size. */ static float vec1DotProduct( int d, const float *A, const float *B ){ float sum = 0.0f; int j = 0; #ifdef VEC1_HAVE_AVX2 # ifndef VEC1_DOT_NACC # define VEC1_DOT_NACC 4 # endif const int nAcc = VEC1_DOT_NACC; int cc; /* Create and initialize to zero nAcc accumulator registers. */ __m256 acc[VEC1_DOT_NACC]; for(cc=0; cc y using row-major matrix A. */ static void vec1RotateVector( int d, const float* A, /* d x d, row-major */ const float* x, /* input vector, length d */ float* y /* output vector, length d */ ){ int i; for(i=0; iaRes = (Vec1AnnResult *)sqlite3_malloc64(sizeof(Vec1AnnResult) * nAlloc); if( p->aRes==0 ){ return SQLITE_NOMEM; } p->nRes = 0; p->nMax = nMax; p->nAlloc = nAlloc; p->bStreaming = bStreaming; p->fMin = INFINITY; return SQLITE_OK; } /* ** A new element, element i, has just been added to the heap as a new leaf ** node. This function repairs the heap by bubbling-up from the new node ** to the root. */ static void vec1HeapBubbleUp(Vec1AnnHeap *p, int i){ int idx = i; while( idx>0 ){ int idxParent = (idx-1)/2; if( p->aRes[idxParent].fDist>p->aRes[idx].fDist ) break; SWAP(Vec1AnnResult, p->aRes[idx], p->aRes[idxParent]); idx = idxParent; } } /* ** A new element has just replaced the heap root node (i.e. the previous ** entry with the largest distance in the heap has been replaced by a ** smaller distance. This function repairs the heap by bubbling-down from ** the new root towards the leaves. */ static void vec1HeapBubbleDown(Vec1AnnHeap *p){ int idx = 0; for(;;){ int idxLeft = idx*2 + 1; int idxRight = idxLeft + 1; int idxMax = idx; if( idxLeftnMax && p->aRes[idxLeft].fDist>p->aRes[idxMax].fDist ){ idxMax = idxLeft; } if( idxRightnMax && p->aRes[idxRight].fDist>p->aRes[idxMax].fDist ){ idxMax = idxRight; } if( idxMax==idx ) break; SWAP(Vec1AnnResult, p->aRes[idx], p->aRes[idxMax]); idx = idxMax; } } /* ** Add an entry to the heap structure. If the heap is already full, the ** entry with the largest fDist value seen so far is discarded. */ static void vec1HeapInsert(Vec1AnnHeap *p, sqlite3_int64 iRowid, double fDist){ if( p->nResnMax ){ p->aRes[p->nRes].iRowid = iRowid; p->aRes[p->nRes].fDist = fDist; vec1HeapBubbleUp(p, (int)p->nRes); p->nRes++; }else if( p->bStreaming ){ if( p->nRes>=p->nAlloc ){ i64 nNew = p->nAlloc * 2; Vec1AnnResult *aNew = 0; if( p->rc==SQLITE_OK ){ i64 nByte = nNew*sizeof(Vec1AnnResult); aNew = (Vec1AnnResult*)sqlite3_realloc64(p->aRes, nByte); } if( aNew==0 ){ p->rc = SQLITE_NOMEM; return; } p->aRes = aNew; p->nAlloc = nNew; } p->aRes[p->nRes].iRowid = iRowid; p->aRes[p->nRes].fDist = fDist; if( fDistaRes[0].fDist ){ SWAP(Vec1AnnResult, p->aRes[0], p->aRes[p->nRes]); vec1HeapBubbleDown(p); } p->nRes++; }else if( fDistaRes[0].fDist ){ p->aRes[0].iRowid = iRowid; p->aRes[0].fDist = fDist; vec1HeapBubbleDown(p); p->fMin = p->aRes[0].fDist; } } /* ** Ensure there is room in the buffer for another nByte bytes of data. That ** is, make sure the allocated size is at least (pBuf->n + nByte). ** ** Return SQLITE_OK if successful, or SQLITE_NOMEM if an OOM error is ** encountered. */ static int vec1BufferGrow(Vec1Buffer *pBuf, sqlite3_int64 nByte){ if( nByte>pBuf->nAlloc-pBuf->n ){ sqlite3_int64 nNew = pBuf->nAlloc; unsigned char *aNew = 0; if( nNew==0 ) nNew = nByte + 100; while( nNew < (pBuf->n + nByte) ){ nNew = nNew * 2; } aNew = sqlite3_realloc64(pBuf->a, nNew); if( aNew==0 ){ return SQLITE_NOMEM; } pBuf->a = aNew; pBuf->nAlloc = (int)nNew; } return SQLITE_OK; } /* ** Ensure that the buffer passed as the first argument has at least nByte ** bytes of space allocated. */ static int vec1BufferSize(Vec1Buffer *pBuf, sqlite3_int64 nByte){ if( nByte>pBuf->nAlloc ){ u8 *aNew = sqlite3_realloc64(pBuf->a, nByte); if( aNew==0 ){ return SQLITE_NOMEM; } pBuf->a = aNew; pBuf->nAlloc = (int)nByte; } return SQLITE_OK; } /* ** Free the allocation and zero the buffer structure. */ static void vec1BufferFree(Vec1Buffer *pBuf){ sqlite3_free(pBuf->a); memset(pBuf, 0, sizeof(Vec1Buffer)); } /* ** Set the error message in context object pCtx to the result of formatting ** zFmt printf() style. */ static void vec1ResultErrorF( sqlite3_context *pCtx, /* Context to set error in */ const char *zFmt, /* printf() style format for error message */ ... /* Trailing arguments for printf() */ ){ char *zMsg = 0; va_list ap; va_start(ap, zFmt); zMsg = sqlite3_vmprintf(zFmt, ap); va_end(ap); sqlite3_result_error(pCtx, zMsg, -1); sqlite3_free(zMsg); } /* ** Attempt to allocate and zero a block of nByte bytes of memory. If ** succesful, return a pointer to the new allocation. It is the responsibility ** of the caller to eventually free the allocation using sqlite3_free(). ** ** If the allocation attempt fails, return 0 and leave an OOM error in ** context object pCtx. */ static void *vec1ContextMalloc(sqlite3_context *pCtx, sqlite3_int64 nByte){ void *pRet = sqlite3_malloc64(nByte); if( pRet==0 ){ sqlite3_result_error_nomem(pCtx); }else{ memset(pRet, 0, nByte); } return pRet; } /* ** Implementation of scalar function vec1_info(). */ static void vec1InfoFunc( sqlite3_context *pCtx, int nVal, sqlite3_value **aVal ){ char *z = 0; const char *zSimd = 0; const char *zThreads = 0; UNUSED_PARAMETER2(nVal, aVal); #if defined(VEC1_HAVE_AVX2) zSimd = "AVX2"; #elif defined(VEC1_HAVE_NEON) zSimd = "NEON"; #else zSimd = "Scalar"; #endif #if VEC1_THREADS zThreads = "multi-threaded"; #else zThreads = "single-threaded"; #endif z = sqlite3_mprintf("version %s (%s, %s)", VEC1_VERSION, zSimd, zThreads); if( z==0 ){ sqlite3_result_error_nomem(pCtx); }else{ sqlite3_result_text(pCtx, z, -1, vec1SqliteFree); } } /* ** Return true if character c counts as whitespace. This is used ** by vec1_from_json(). */ static int vec1_isspace(char c){ return (c==' '); } /* ** Implementation of scalar function vec1_from_json(). Converts a json array ** of numbers into a vector in vec1 native format (32-bit little-endian ** floats). */ static void vec1FromJsonFunc( sqlite3_context *pCtx, int nVal, sqlite3_value **aVal ){ const char *zJson = (const char*)sqlite3_value_text(aVal[0]); int nJson = sqlite3_value_bytes(aVal[0]); const char *p = zJson; assert( nVal==1 ); UNUSED_PARAMETER(nVal); float *aVec = 0; int nVec = 0; /* vec1_from_json(NULL) returns NULL */ if( sqlite3_value_type(aVal[0])==SQLITE_NULL ) return; /* Allocate space for the return value */ aVec = (float*)sqlite3_malloc(sizeof_f32*(nJson/2)); if( !aVec ){ sqlite3_result_error_nomem(pCtx); return; } while( vec1_isspace(*p) ) p++; if( *p!='[' ) goto parse_failed; p++; while( 1 ){ char *p2 = 0; double rVal = 0.0; while( vec1_isspace(*p) ) p++; rVal = strtod(p, &p2); if( p==p2 ) goto parse_failed; p = p2; aVec[nVec] = (float)rVal; nVec++; while( vec1_isspace(*p) ) p++; if( *p==']' ){ p++; break; } if( *p!=',' ) goto parse_failed; p++; } while( vec1_isspace(*p) ) p++; if( *p ) goto parse_failed; sqlite3_result_blob(pCtx, (void*)aVec, nVec*sizeof_f32, vec1SqliteFree); return; parse_failed: sqlite3_free(aVec); sqlite3_result_error(pCtx, "error parsing json vector", -1); } /* ** SQL Scalar Functions: ** ** vec1_to_json(BLOB) ** vec1_to_json_f(BLOB) ** vec1_to_json_i(BLOB) */ static void vec1ToJsonGeneric( sqlite3_context *pCtx, int eType, int nBytePerElem, sqlite3_value *pVal ){ int ii; sqlite3 *db = sqlite3_context_db_handle(pCtx); sqlite3_str *pStr = 0; int n = sqlite3_value_bytes(pVal); const void *a = sqlite3_value_blob(pVal); if( n==0 || (n%nBytePerElem)!=0 ){ sqlite3_result_error(pCtx, "vec1: invalid vector argument", -1); return; } n = n / nBytePerElem; pStr = sqlite3_str_new(db); if( eType==VEC1_TYPE_FLOAT32 ){ const float *aFloat = (const float*)a; sqlite3_str_appendf(pStr, "[%.9g", aFloat[0]); for(ii=1; ii> 56) & 0xFF; aBuf[1] = (val >> 48) & 0xFF; aBuf[2] = (val >> 40) & 0xFF; aBuf[3] = (val >> 32) & 0xFF; aBuf[4] = (val >> 24) & 0xFF; aBuf[5] = (val >> 16) & 0xFF; aBuf[6] = (val >> 8) & 0xFF; aBuf[7] = (val >> 0) & 0xFF; } /* ** Read and return a 64-bit unsigned big-endian integer from buffer aBuf[]. */ static u64 vec1GetU64(const u8 *aBuf){ return (((u64)aBuf[0]) << 56) + (((u64)aBuf[1]) << 48) + (((u64)aBuf[2]) << 40) + (((u64)aBuf[3]) << 32) + (((u64)aBuf[4]) << 24) + (((u64)aBuf[5]) << 16) + (((u64)aBuf[6]) << 8) + (((u64)aBuf[7]) << 0); } /* ** Subtract a2 from a1 and leave the result in aOut. i.e. ** ** aOut = a1 - a2 */ static void vec1Sub(float *aOut, const float *a1, const float *a2, int n){ int i; for(i=0; i /* ** Unix version of thread object. */ struct Vec1Thread { pthread_t tid; }; /* ** Unix version of mutex object. */ struct Vec1Mutex { pthread_mutex_t mutex; }; /* ** Unix version of condition variable. */ struct Vec1Cond { pthread_cond_t cond; }; /* ** Create a new thread running xTask(pIn). If successful, set *ppThread ** to the new thread handle and return SQLITE_OK. Otherwise, if an ** error occurs, set *ppThread to 0 and return either SQLITE_ERROR ** or SQLITE_NOMEM as appropriate. */ static int vec1ThreadCreate( Vec1Thread **ppThread, void *(*xTask)(void*), void *pIn ){ Vec1Thread *p = sqlite3_malloc(sizeof(Vec1Thread)); if( p==0 ){ *ppThread = 0; return SQLITE_NOMEM; } if( pthread_create(&p->tid, 0, xTask, pIn)!=0 ){ sqlite3_free(p); *ppThread = 0; return SQLITE_ERROR; } *ppThread = p; return SQLITE_OK; } /* ** Join the thread pThread. If successful set *ppOut to the value ** returned by the thread's xTask routine and return SQLITE_OK. ** Otherwise, return SQLITE_ERROR and set *ppOut to 0. ** ** The thread handle may not be used after this function has been ** called. The results of doing so are undefined. */ static int vec1ThreadJoin(Vec1Thread *pThread, void **ppOut){ int rc = SQLITE_OK; if( pthread_join(pThread->tid, ppOut)!=0 ){ rc = SQLITE_ERROR; if( ppOut ) *ppOut = 0; } sqlite3_free(pThread); return rc; } /* ** Create a new mutex. If successful, set *ppMutex to the new ** mutex handle and return SQLITE_OK. Otherwise, set *ppMutex to 0 ** and return an SQLite error code - SQLITE_NOMEM or SQLITE_ERROR as ** appropriate. */ static int vec1MutexCreate(Vec1Mutex **ppMutex){ Vec1Mutex *p = sqlite3_malloc(sizeof(Vec1Mutex)); if( p==0 ){ *ppMutex = 0; return SQLITE_NOMEM; } if( pthread_mutex_init(&p->mutex, 0)!=0 ){ sqlite3_free(p); *ppMutex = 0; return SQLITE_ERROR; } *ppMutex = p; return SQLITE_OK; } /* ** Destroy (free) the mutex passed as the only argument. */ static void vec1MutexDestroy(Vec1Mutex *pMutex){ pthread_mutex_destroy(&pMutex->mutex); sqlite3_free(pMutex); } /* ** Enter the mutex passed as the only argument. The results of calling ** this function on a mutex that is already held are undefined. */ static void vec1MutexEnter(Vec1Mutex *pMutex){ pthread_mutex_lock(&pMutex->mutex); } /* ** Leave the mutex passed as the only argument. */ static void vec1MutexLeave(Vec1Mutex *pMutex){ pthread_mutex_unlock(&pMutex->mutex); } /* ** Create a new posix style condition variable. */ static int vec1CondCreate(Vec1Cond **ppCond){ Vec1Cond *p = sqlite3_malloc(sizeof(Vec1Cond)); if( p==0 ){ *ppCond = 0; return SQLITE_NOMEM; } if( pthread_cond_init(&p->cond, 0)!=0 ){ sqlite3_free(p); *ppCond = 0; return SQLITE_ERROR; } *ppCond = p; return SQLITE_OK; } /* ** Destroy the condition variable passed as the only argument. */ static void vec1CondDestroy(Vec1Cond *pCond){ pthread_cond_destroy(&pCond->cond); sqlite3_free(pCond); } /* ** Mutex pMutex must be held when this is called. It causes the ** thread to atomically relinquish the mutex and wait on the ** condition variable. When the condition is signaled by another ** thread calling vec1CondBroadcast(), this thread will reaquire ** mutex pMutex and continue. */ static void vec1CondWait(Vec1Mutex *pMutex, Vec1Cond *pCond){ pthread_cond_wait(&pCond->cond, &pMutex->mutex); } /* ** Wake up threads waiting on the condition variable passed as ** the only argument. */ static void vec1CondBroadcast(Vec1Cond *pCond){ pthread_cond_broadcast(&pCond->cond); } #endif /* SQLITE_OS_UNIX && defined(SQLITE_MUTEX_PTHREADS) */ /******************************** End Unix Pthreads *************************/ /********************************* Win32 Threads ****************************/ #if defined(VEC1_THREADS_WINDOWS) #include struct Vec1Thread { HANDLE hThread; void *(*xTask)(void*); void *pIn; void *pOut; }; struct Vec1Mutex { CRITICAL_SECTION cs; }; struct Vec1Cond { CONDITION_VARIABLE cv; }; /* Windows thread trampoline - WINAPI calling convention */ static DWORD WINAPI vec1ThreadMain(LPVOID pArg){ Vec1Thread *p = (Vec1Thread*)pArg; p->pOut = p->xTask(p->pIn); return 0; } static int vec1ThreadCreate( Vec1Thread **ppThread, void *(*xTask)(void*), void *pIn ){ Vec1Thread *p = sqlite3_malloc(sizeof(Vec1Thread)); if( p==0 ){ *ppThread = 0; return SQLITE_NOMEM; } p->xTask = xTask; p->pIn = pIn; p->pOut = 0; p->hThread = CreateThread(NULL, 0, vec1ThreadMain, p, 0, NULL); if( p->hThread==NULL ){ sqlite3_free(p); *ppThread = 0; return SQLITE_ERROR; } *ppThread = p; return SQLITE_OK; } static int vec1ThreadJoin(Vec1Thread *pThread, void **ppOut){ int rc = SQLITE_OK; if( WaitForSingleObject(pThread->hThread, INFINITE)!=WAIT_OBJECT_0 ){ rc = SQLITE_ERROR; if( ppOut ) *ppOut = 0; }else{ if( ppOut ) *ppOut = pThread->pOut; } CloseHandle(pThread->hThread); sqlite3_free(pThread); return rc; } static int vec1MutexCreate(Vec1Mutex **ppMutex){ Vec1Mutex *p = sqlite3_malloc(sizeof(Vec1Mutex)); if( p==0 ){ *ppMutex = 0; return SQLITE_NOMEM; } InitializeCriticalSection(&p->cs); *ppMutex = p; return SQLITE_OK; } static void vec1MutexDestroy(Vec1Mutex *pMutex){ DeleteCriticalSection(&pMutex->cs); sqlite3_free(pMutex); } static void vec1MutexEnter(Vec1Mutex *pMutex){ EnterCriticalSection(&pMutex->cs); } static void vec1MutexLeave(Vec1Mutex *pMutex){ LeaveCriticalSection(&pMutex->cs); } static int vec1CondCreate(Vec1Cond **ppCond){ Vec1Cond *p = sqlite3_malloc(sizeof(Vec1Cond)); if( p==0 ){ *ppCond = 0; return SQLITE_NOMEM; } InitializeConditionVariable(&p->cv); *ppCond = p; return SQLITE_OK; } static void vec1CondDestroy(Vec1Cond *pCond){ /* Windows condition variables have no destroy function */ sqlite3_free(pCond); } static void vec1CondWait(Vec1Mutex *pMutex, Vec1Cond *pCond){ SleepConditionVariableCS(&pCond->cv, &pMutex->cs, INFINITE); } static void vec1CondBroadcast(Vec1Cond *pCond){ WakeAllConditionVariable(&pCond->cv); } #endif /* SQLITE_OS_WIN_THREADS */ /******************************** End Win32 Threads *************************/ /* ***************************************************************************** *****************************************************************************/ typedef struct Vec1JobQueue Vec1JobQueue; #if VEC1_THREADS typedef struct Vec1Job Vec1Job; struct Vec1Job { void *pCtx; void (*xWork)(void *pCtx); int (*xFinish)(void *pCtx, int); Vec1Job *pJobNext; }; /* ** nJob is the number of jobs on either the pWaiting or pFinished list, ** or else being worked on by a thread. nJob is incremented when the ** job is added to pWaiting, decremented when it is removed from pFinished. ** Always by the main thread. */ struct Vec1JobQueue { Vec1Mutex *pMutex; Vec1Cond *pCond1; Vec1Cond *pCond2; Vec1Thread **apWorker; int nWorker; /* Number of worker threads */ int bDone; /* True if threads should exit */ int nJob; /* Total number of unfinished jobs */ Vec1Job *pWaiting; /* List of jobs waiting for xWork() */ Vec1Job *pFinished; /* List of jobs waiting for xFinish() */ Vec1Job *pFree; /* List of spare Vec1Job structures */ }; static void *vec1JobWorkerMain(void *pCtx){ Vec1JobQueue *p = (Vec1JobQueue*)pCtx; while( 1 ){ Vec1Job *pJob = 0; /* Try to find a new job to work on. */ vec1MutexEnter(p->pMutex); while( p->pWaiting==0 && p->bDone==0 ){ vec1CondWait(p->pMutex, p->pCond1); } pJob = p->pWaiting; if( pJob ) p->pWaiting = pJob->pJobNext; vec1MutexLeave(p->pMutex); /* If pJob is 0, we're done. Thread should exit. */ if( pJob==0 ) break; /* Do the work for this job. */ pJob->xWork(pJob->pCtx); /* Put the job on the finished list. If it is currently empty, the ** main thread may be waiting. Signal it in that case. */ vec1MutexEnter(p->pMutex); if( p->pFinished==0 ) vec1CondBroadcast(p->pCond2); pJob->pJobNext = p->pFinished; p->pFinished = pJob; vec1MutexLeave(p->pMutex); } return 0; } static int vec1JobQueueFinishJobs(Vec1JobQueue *pQueue, int rcin){ int rc = rcin; if( pQueue==0 ) return rc; while( 1 ){ Vec1Job *pFin = 0; Vec1Job *pWork = 0; /* Try to find a job to help with. If there is nothing, wait on pCond2. */ vec1MutexEnter(pQueue->pMutex); while( pQueue->pFinished==0 && pQueue->pWaiting==0 && pQueue->nJob>0 ){ vec1CondWait(pQueue->pMutex, pQueue->pCond2); } if( pQueue->pFinished ){ pFin = pQueue->pFinished; pQueue->pFinished = pFin->pJobNext; pQueue->nJob--; }else if( pQueue->pWaiting ){ pWork = pQueue->pWaiting; pQueue->pWaiting = pWork->pJobNext; pQueue->nJob--; } vec1MutexLeave(pQueue->pMutex); if( pFin==0 && pWork==0 ) break; if( pWork ){ pWork->xWork(pWork->pCtx); pFin = pWork; } { int rc2 = pFin->xFinish(pFin->pCtx, rc); if( rc==SQLITE_OK ) rc = rc2; } vec1MutexEnter(pQueue->pMutex); pFin->pJobNext = pQueue->pFree; pQueue->pFree = pFin; vec1MutexLeave(pQueue->pMutex); } return rc; } static void vec1JobQueueFree(Vec1JobQueue *pQueue){ if( pQueue ){ if( pQueue->pMutex && pQueue->pCond1 ){ int ii; vec1JobQueueFinishJobs(pQueue, SQLITE_ERROR); vec1MutexEnter(pQueue->pMutex); pQueue->bDone = 1; vec1CondBroadcast(pQueue->pCond1); vec1MutexLeave(pQueue->pMutex); for(ii=0; iinWorker; ii++){ void *dummy = 0; vec1ThreadJoin(pQueue->apWorker[ii], &dummy); } } if( pQueue->pMutex ) vec1MutexDestroy(pQueue->pMutex); if( pQueue->pCond1 ) vec1CondDestroy(pQueue->pCond1); if( pQueue->pCond2 ) vec1CondDestroy(pQueue->pCond2); assert( pQueue->pWaiting==0 ); assert( pQueue->pFinished==0 ); assert( pQueue->pFree!=0 ); sqlite3_free(pQueue); } } /* ** Allocate a new job-queue. */ static Vec1JobQueue *vec1JobQueueNew(int nWorker){ int rc = SQLITE_OK; Vec1JobQueue *pNew = 0; int nJob = nWorker + 8; int nByte = sizeof(Vec1JobQueue) + sizeof(Vec1Thread*)*nWorker + sizeof(Vec1Job) * nJob; assert( nWorker>=0 ); pNew = (Vec1JobQueue*)vec1MallocZero(nByte); if( pNew==0 ){ rc = SQLITE_NOMEM; }else{ int ii; Vec1Job *pJob; pNew->apWorker = (Vec1Thread**)&pNew[1]; pJob = (Vec1Job*)&pNew->apWorker[nWorker]; for(ii=0; iipJobNext = pNew->pFree; pNew->pFree = pJob; pJob++; } rc = vec1MutexCreate(&pNew->pMutex); if( rc==SQLITE_OK ) rc = vec1CondCreate(&pNew->pCond1); if( rc==SQLITE_OK ) rc = vec1CondCreate(&pNew->pCond2); for(ii=0; rc==SQLITE_OK && iiapWorker[ii], vec1JobWorkerMain, pNew); if( rc==SQLITE_OK ){ pNew->nWorker++; } } } if( rc!=SQLITE_OK ){ vec1JobQueueFree(pNew); pNew = 0; } return pNew; } static int vec1DummyFinal(void *pCtx, int rcin){ UNUSED_PARAMETER(pCtx); return rcin; } static int vec1JobQueueAddJob( Vec1JobQueue *pQueue, void (*xWork)(void *pCtx), int (*xFinish)(void *pCtx, int), void *pCtx ){ Vec1Job *pFree = 0; Vec1Job *pFinish = 0; Vec1Job *pWork = 0; int rc = SQLITE_OK; if( xFinish==0 ) xFinish = vec1DummyFinal; if( pQueue==0 ){ xWork(pCtx); return xFinish(pCtx, SQLITE_OK); } /* Check if this thread needs to do any xFinish() or xWork() calls. And ** try to get a free Vec1Job object at the same time. */ vec1MutexEnter(pQueue->pMutex); assert( pQueue->pWaiting || pQueue->pFree || pQueue->pFinished ); if( pQueue->pFree ){ pFree = pQueue->pFree; pQueue->pFree = pFree->pJobNext; }else if( pQueue->pFinished ){ pFinish = pQueue->pFinished; pQueue->pFinished = pFinish->pJobNext; pQueue->nJob--; }else{ pWork = pQueue->pWaiting; pQueue->pWaiting = pWork->pJobNext; pQueue->nJob--; } vec1MutexLeave(pQueue->pMutex); /* At most 1 of pFree, pFinish and pWork may be non NULL */ assert( (pFree!=0)+(pFinish!=0)+(pWork!=0)<=1 ); if( pWork ){ pWork->xWork(pWork->pCtx); pFinish = pWork; } if( pFinish ){ rc = pFinish->xFinish(pFinish->pCtx, SQLITE_OK); pFree = pFinish; } assert( pFree ); pFree->pCtx = pCtx; pFree->xWork = xWork; pFree->xFinish = xFinish; vec1MutexEnter(pQueue->pMutex); if( pQueue->pWaiting==0 ) vec1CondBroadcast(pQueue->pCond1); pFree->pJobNext = pQueue->pWaiting; pQueue->pWaiting = pFree; pQueue->nJob++; vec1MutexLeave(pQueue->pMutex); return rc; } #else /* VEC1_THREADS=0 */ /* ** Dummy implementation of job-queue for single-threaded builds. */ static int vec1JobQueueFinishJobs(Vec1JobQueue *pQueue, int rcin){ return rcin; } static void vec1JobQueueFree(Vec1JobQueue *pQueue){} static Vec1JobQueue *vec1JobQueueNew(int a){ return 0; } static int vec1JobQueueAddJob( Vec1JobQueue *pQueue, void (*xWork)(void *pCtx), int (*xFinish)(void *pCtx, int), void *pCtx ){ xWork(pCtx); return xFinish ? xFinish(pCtx, SQLITE_OK) : SQLITE_OK; } #endif /* ** Minimum and maximum values allowed for the 'blocksize' command. */ #define VEC1_BLOCKSIZE_MIN 1024 #define VEC1_BLOCKSIZE_MAX (16*1024*1024) #define VEC1_BLOCKSIZE_DEFAULT (1024*1024) #define VEC1_BLOCKSIZE_MIN_DEFAULT (16*1024) #define VEC1_VECSIZE_MIN 2 #define VEC1_VECSIZE_MAX 4096 /* ** Number of codes in each codebook. This is always 256. */ #define VEC1_PQ_CODEBOOK_SZ 256 #define VEC1_MIN_CODESIZE 1 #define VEC1_MAX_CODESIZE 256 #define VEC1_MIN_BUCKET 0 #define VEC1_MAX_BUCKET 65536 #define VEC1_MIN_NOPQ_ROUND 1 #define VEC1_MAX_NOPQ_ROUND 32 #define VEC1_PQ_KMEANS_NITER 30 #define VEC1_PQ_KMEANS_STOPVAL 0.999 /* ** Minimum number of vectors required for PQ training. */ #define VEC1_PQ_TRAINING_SET_MIN (VEC1_PQ_CODEBOOK_SZ * 2) #define VEC1_TRAINING_TOTAL 0 #define VEC1_TRAINING_ROT_CODEBOOKS 1 #define VEC1_TRAINING_ROT_COVARIANCE 2 #define VEC1_TRAINING_ROT_JACOBI 3 #define VEC1_TRAINING_ROT_VECTORS 4 #define VEC1_TRAINING_COARSE_INIT 5 #define VEC1_TRAINING_COARSE_KMEANS 6 #define VEC1_TRAINING_RESIDUALS 7 #define VEC1_TRAINING_CODEBOOKS 8 #define VEC1_TRAINING_NTIMER 9 /* ** When accumulating training data, allocate space in chunks of ** VEC1_TRAINING_SZCHUNK. Allow at most VEC1_TRAINING_MAXCHUNK chunks of ** training data. With default values, the limit is 64GiB of training ** vectors. */ #define VEC1_TRAINING_MAXCHUNK 1024 #define VEC1_TRAINING_SZCHUNK (32*1024*1024) /* ** Values for Vec1TrainCtx.eQuant */ #define VEC1_QUANTIZE_NONE 0 #define VEC1_QUANTIZE_PQ 1 #define VEC1_QUANTIZE_OPQ 2 #define VEC1_QUANTIZE_BQ 3 /* ** Training vectors collected by xStep() are stored in an instance ** of this structure. */ typedef struct Vec1TrainVectors Vec1TrainVectors; struct Vec1TrainVectors { int nElem; /* Number of elements in each vector */ int nVec; /* Number of vectors in chunk array */ int nChunk; /* Number of chunks allocated so far */ int nVecPerChunk; /* Number of vectors in each chunk */ float *aChunk[VEC1_TRAINING_MAXCHUNK]; }; /* ** Context object for the vec1_train() aggregate function. ** ** eQuant: ** A VEC1_QUANTIZE_XYZ value. Set by the "quantizer" option. ** ** nCodesize: ** For PQ or OPQ, the number of vector sub-spaces. For BQ, the number of ** bytes required to store the bit-vector. For 1 bit per dimension this ** value is set to ((d+7)/8). ** ** nBucket: ** Number of IVF buckets used by coarse quantizer. Set to 0 to disable ** coarse quant altogether. */ typedef struct Vec1TrainCtx Vec1TrainCtx; struct Vec1TrainCtx { /* Training vectors collected by xStep() */ Vec1TrainVectors tv; /* Training parameters configured by json argument */ int eDistance; /* VEC1_DISTANCE_L2 or COS */ int eQuant; /* VEC1_QUANTIZE_XX value */ int nCodesize; /* Number of vector sub-spaces */ int nBucket; /* Number of IVF buckets */ int bOpq; int bResidual; int bSvdVerify; int bWHT; /* Use WHT transformation */ int nThread; /* Number of CPU threads to use */ int nOpqRound; /* Number of OPQ rounds */ char *zLogFunction; /* Name of user SQL function for logging */ char *zProfileFunction; /* Name of user SQL function for profile */ sqlite3_stmt *pLogStmt; /* SELECT zLogFunction(?, ?) */ sqlite3_context *pCtx; /* Context object for errors */ int rc; /* Error code for training operation */ /* Used for calculating "percent" value passed to "SELECT zLogFunction()" */ int nTotalWork; int nCompletedWork; /* Calculated based on training parameters + size of first vector */ int nCodeElem; /* Number of elements in codebook vectors */ u64 aTime[VEC1_TRAINING_NTIMER]; }; #define VEC1_TRAINING_WORK_OPQ 2 #define VEC1_TRAINING_WORK_COARSE1 1 #define VEC1_TRAINING_WORK_COARSE2 1 #define VEC1_TRAINING_WORK_CODEBOOKS 1 /* ** Return a pointer to the requested training vector. The results are ** undefined if parameter iVec is out of range. */ static float *vec1TrainingVector(Vec1TrainVectors *p, int iVec){ int iChunk = iVec / p->nVecPerChunk; int iOff = (iVec % p->nVecPerChunk) * p->nElem; return &((p->aChunk[iChunk])[iOff]); } /* ** aSub[] is a vector of nCodeElem elements. aCodebook[] is a codebook of ** nCode vectors, each of nCodeElem elements. Return the index of the best ** match for aSub in aCodebook[]. */ static int vec1BestMatchSimple( const float *aCodebook, /* Codebook of nCodeElem element vectors */ int nCode, /* Number of vectors in aCodebook */ const float *aSub, /* nCodeElem element vector */ int nCodeElem, /* Size of vectors in elements */ double *pfBest /* Distance to best match */ ){ double fBestDist = INFINITY; int iBest = 0; int ii; for(ii=0; ii=0 && iBestaVec, p->nElem, i) typedef struct LloydsJob LloydsJob; struct LloydsJob { /* Data to train on */ Vec1TrainVectors *pVec; int iFirst; /* First vector for this job */ int iEof; /* Index of last vector +1 for this job */ int nK; /* Number of centroids */ const float *aCentroid; /* Centroids */ #ifdef VEC1_USE_TRANSPOSITION float *aTrans; /* Transposed centroids */ #endif /* Outputs */ double fTotal; float *aSum; int *aCount; }; /* ** Context object for simple PRNG. See functions vec1RandomInit() and ** vec1RandomNext() for details. */ typedef struct Vec1Random Vec1Random; struct Vec1Random { u64 state; u64 inc; }; static u32 vec1RandomNext(Vec1Random *p) { u32 xorshifted, rot; u64 old = p->state; p->state = old * 6364136223846793005ULL + p->inc; xorshifted = (u32)(((old >> 18u) ^ old) >> 27u); rot = (u32)(old >> 59u); return ((xorshifted >> rot) | (xorshifted << ((32 - rot) & 31))) & 0x7FFFFFFF; } static void vec1RandomInit(Vec1Random *p, u64 seed, u64 id){ p->state = 0; p->inc = (id << 1u) | 1u; vec1RandomNext(p); p->state += seed; vec1RandomNext(p); } typedef struct KMeansInitJob KMeansInitJob; struct KMeansInitJob { int iFirst; /* First vector for this job */ int iEof; /* Index of last vector +1 for this job */ double fTotalMin; /* Total of aMin[iFirst..iEof-1] */ Vec1TrainVectors *pVec; u32 *aWeight; float *aMin; int *aBest; float *aCentroid; int nCentroid; int iFirstNew; }; static void vec1KMeansInitWork(void *pCtx){ KMeansInitJob *p = (KMeansInitJob*)pCtx; const int nElem = p->pVec->nElem; double fTotalMin = 0.0; int ii; for(ii=p->iFirst; iiiEof; ii++){ int jj; float *vec = vec1TrainingVector(p->pVec, ii); for(jj=p->iFirstNew; jjnCentroid; jj++){ float *cent = &p->aCentroid[jj * nElem]; float dist = (float)vec1L2Dist(vec, cent, nElem); if( jj==0 || distaMin[ii] ){ p->aMin[ii] = dist; if( p->aBest ) p->aBest[ii] = jj; } } fTotalMin += ((double)p->aMin[ii] * (p->aWeight ? p->aWeight[ii] : 1)); } p->fTotalMin = fTotalMin; } /* ** Macro used to assign vectors to jobs in cases where an algorithm ** parallelizes across vectors. The pJob object must have at least the ** following member: ** ** struct XyzJob { ** ... ** Vec1TrainVectors *pVec; // Training vectors to use a subset of ** int iFirst; // index of first vector in pVec to use ** int iEof; // 1 past index of last vector in pVec to use ** ... ** }; */ #define ASSIGN_VECTORS_TO_JOB(pV, pJob, iJob, nJob) \ pJob->pVec = (pV); \ pJob->iFirst = (iJob==0) ? 0 : pJob[-1].iEof; \ pJob->iEof = pJob->iFirst + ((pV)->nVec - pJob->iFirst) / (nJob - iJob); \ assert( pJob->iEof<=(pV)->nVec ); \ assert( (iJob==nJob-1)==(pJob->iEof==(pV)->nVec) ); /* ** */ static int vec1KMeansInitOne( Vec1TrainVectors *pVec, u32 *aWeight, Vec1JobQueue *pQueue, /* Job queue to use (NULL -> 1 thread) */ int nK, /* Required number of outputs */ int nPerRound, /* Number of centroids per round */ int nTail, /* Number of tail rounds */ float *aOut, /* Array to populate with centroids */ u32 *aHist /* Array to populate with histogram */ ){ const int nJob = (pQueue ? pQueue->nWorker + 1 : 1); const int nElem = pVec->nElem; const int nVByte = nElem * sizeof_f32; Vec1Random rnd; int nByte; /* Bytes of space to allocate */ KMeansInitJob *aJob = 0; float *aMin = 0; int *aBest = 0; int ii; /* Loop counter for per-job loop */ int rr; /* Loop counter for per-round loop */ int iNext; int nCentroid = 0; int nNewCentroid = 0; float *vec = 0; vec1RandomInit(&rnd, vec1Rand31(), 0); nByte = (sizeof(KMeansInitJob) * nJob) + (pVec->nVec * sizeof_f32) + (aHist ? pVec->nVec * sizeof_u32 : 0); aJob = vec1MallocZero(nByte); if( aJob==0 ){ return SQLITE_NOMEM; } aMin = (float*)&aJob[nJob]; if( aHist ){ aBest = (int*)&aMin[pVec->nVec]; } for(ii=0, iNext=0; iiaMin = aMin; pJob->aBest = aBest; pJob->aWeight = aWeight; pJob->aCentroid = aOut; } /* First centroid is allocated by random selection. */ vec = vec1TrainingVector(pVec, vec1RandomNext(&rnd)%pVec->nVec); memcpy(aOut, vec, nVByte); nCentroid = nNewCentroid = 1; for(rr=1; rrnVec-1; vv++){ fTarget -= (aMin[vv] * (aWeight ? aWeight[vv] : 1)); if( fTarget<=0.0 ) break; } memcpy(&aOut[nCentroid*nElem], vec1TrainingVector(pVec, vv), nVByte); nCentroid++; }else{ /* KMeans || case */ int cc; vv = vec1RandomNext(&rnd) % pVec->nVec; for(cc=0; ccnVec; } memcpy(&aOut[nCentroid*nElem], vec1TrainingVector(pVec, vv), nVByte); nCentroid++; } } } if( aHist ){ int vv; for(vv=0; vvnVec; vv++){ aHist[aBest[vv]]++; } } sqlite3_free(aJob); return SQLITE_OK; } /* ** Implementation of KMeans || initialization. */ static int vec1KMeansParallel( Vec1TrainVectors *pVec, Vec1JobQueue *pQueue, /* Job queue to use (NULL -> 1 thread) */ int nK, /* Required number of outputs */ float *aOut /* Array to populate with centroids */ ){ int rc = SQLITE_OK; const int nCent = nK * 2; const int nByte = nCent * (pVec->nElem * sizeof_f32 + sizeof_u32); float *aCent; aCent = (float*)vec1MallocZero(nByte); if( aCent==0 ){ rc = SQLITE_NOMEM; }else{ u32 *aWeight = (u32*)&aCent[pVec->nElem * nCent]; rc = vec1KMeansInitOne(pVec, 0, pQueue, nCent, nCent/10, 0, aCent,aWeight); if( rc==SQLITE_OK ){ Vec1TrainVectors vv; memset(&vv, 0, sizeof(vv)); vv.nElem = pVec->nElem; vv.nVec = nCent; vv.nVecPerChunk = nCent; vv.nChunk = 1; vv.aChunk[0] = aCent; rc = vec1KMeansInitOne(&vv, aWeight, pQueue, nK, nK, 0, aOut, 0); } sqlite3_free(aCent); } return rc; } static int vec1KMeansPlusPlus( Vec1TrainVectors *pVec, Vec1JobQueue *pQueue, /* Job queue to use (NULL -> 1 thread) */ int nK, /* Required number of outputs */ float *aOut /* Array to populate with centroids */ ){ const int nPerRound = 8; const int nTailRound = 32; return vec1KMeansInitOne(pVec, 0, pQueue, nK, nPerRound, nTailRound, aOut, 0); } #if defined(_MSC_VER) # define VEC1_NOINLINE __declspec(noinline) #else # define VEC1_NOINLINE __attribute__((noinline)) #endif static VEC1_NOINLINE void vec1AddInPlaceScalar( float * a1, const float * a2, int n ){ int i; for(i=0; ipVec; int nElem = pVec->nElem; int ii; p->fTotal = 0.0f; memset(p->aSum, 0, sizeof_f32 * p->nK * nElem); memset(p->aCount, 0, sizeof(int) * p->nK); /* Loop through those vectors that are our responsibility, ** VEC1_MULTIMATCH_NVEC vectors at a time. Each iteration of this outer ** loop will compare VEC1_MULTIMATCH_NVEC training vectors to all candidate ** centroids, and update p->aCount[] and p->aSum[] with the results. */ ii = p->iFirst; for( ; ii<=(p->iEof-VEC1_MULTIMATCH_NVEC); ii+=VEC1_MULTIMATCH_NVEC){ int vv; const float *aVec[VEC1_MULTIMATCH_NVEC]; int aBest[VEC1_MULTIMATCH_NVEC]; /* Initialize the training vector array with the VEC1_MULTIMATCH_NVEC ** training vectors that will be considered by this loop. */ for(vv=0; vvaCentroid, p->aTrans, p->nK, aVec, aBest, &p->fTotal ); /* Scalar array aBest[] now contains the index of the best centroid ** for each of the VEC1_MULTIMATCH_NVEC training vectors being considered ** by this loop. Update aCount[] and aSum[] avvordingly. */ for(vv=0; vvaCount[iBest]++; vec1AddInPlace(&p->aSum[iBest*nElem], aVec[vv], nElem); } #if 0 /* Check (assert) using vec1BestMatchSimple() that the fancy code above ** actually worked. */ for(vv=0; vvaCentroid, p->nK, aVec[vv], nElem, 0 ); if( iBest2!=aBest[vv] ){ const float *c1 = &p->aCentroid[aBest[vv] * nElem]; const float *c2 = &p->aCentroid[iBest2 * nElem]; float d1 = vec1L2Dist(aVec[vv], c1, nElem); float d2 = vec1L2Dist(aVec[vv], c2, nElem); assert( d1==d2 || (d1>=d2 && ((d1 - d2) / d1) < 0.00001) || (d2>d1 && ((d2 - d1) / d2) < 0.00001) ); } } #endif } /* Scalar tail - handle training vectors that were not done above as ** part of a batch of VEC1_MULTIMATCH_NVEC. */ for(; iiiEof; ii++){ const float *aSub = vec1TrainingVector(pVec, ii); int iBest = vec1BestMatchSimple( p->aCentroid, p->nK, aSub, nElem, &p->fTotal ); assert( iBest>=0 && iBestnK ); p->aCount[iBest]++; vec1AddInPlace(&p->aSum[iBest * nElem], aSub, nElem); } } /* ** Parameter aIn points to an array of centroids containing nK vectors, ** each nElem elements in size. i.e. so that element j of vector i is ** stored in aIn[i*nElem + j]. Total size of the array is nK*nElem values. ** ** Parameter aOut[] also points to an array of nK*nElem elements. This ** function populates that array with the transposed version of the codebook ** before returning. */ static void vec1CodebookTranspose( const float *aIn, /* Centroid array in on-disk format */ int nElem, /* Number of elements in each vectors */ int nWidth, /* Transpose width */ int nK, /* Number of centroids */ float *aOut /* Populate this array with transformation */ ){ int K, d; float *pOut = aOut; /* If nK is not divisible by nWidth, just ignore any tail vectors. */ assert( (nK % nWidth)==0 || nWidth==VEC1_MULTIMATCH_NCENT ); nK = (nK / nWidth) * nWidth; for(K=0; KpLogStmt && p->rc==SQLITE_OK ){ int percent = ((100*p->nCompletedWork) / p->nTotalWork); char *zMsg = 0; va_list ap; va_start(ap, zFmt); zMsg = sqlite3_vmprintf(zFmt, ap); va_end(ap); if( zMsg==0 ){ p->rc = SQLITE_NOMEM; }else{ sqlite3_bind_int(p->pLogStmt, 1, percent); sqlite3_bind_text(p->pLogStmt, 2, zMsg, -1, SQLITE_TRANSIENT); sqlite3_step(p->pLogStmt); sqlite3_free(zMsg); p->rc = sqlite3_reset(p->pLogStmt); } if( p->rc==SQLITE_NOMEM ){ sqlite3_result_error_nomem(p->pCtx); }else if( p->rc!=SQLITE_OK ){ sqlite3 *db = sqlite3_db_handle(p->pLogStmt); vec1ResultErrorF(p->pCtx, "%s", sqlite3_errmsg(db)); } } } #define START_TRAINING_TIMER(p, i) { p->aTime[i] -= vec1HardwareTimer(); } #define END_TRAINING_TIMER(p, i) { p->aTime[i] += vec1HardwareTimer(); } static int vec1Ann1KMeans( Vec1TrainCtx *p, /* Training data + parameters */ Vec1TrainVectors *pVec, /* Vectors to train K-Means on */ Vec1JobQueue *pQueue, /* Job-queue, or NULL for single thread */ int nK, /* Value of K in K-means */ float *aCode /* OUT: Populate this array */ ){ i64 nByte = 0; int ii; int iIter; int nElem = pVec->nElem; int *pCsr = 0; double fPrevTotal = 0.0; int *aCount = 0; float *aSum = 0; LloydsJob *aJob = 0; int rc = SQLITE_OK; int nThread = p ? p->nThread : 1; /* Allocate array of jobs to use for multi-threaded Lloyd's algorithm. ** We use this even if there are no worker threads - in that case ** allocate an array of 1. The algorithm parallelizes across vectors - each ** jobs is assigned a subset of the vectors to work on. ** ** Each job requires the following working space: ** ** + aCount -> (sizeof(int) * K) bytes ** + aSum -> (sizeof_f32 * nElem * K) bytes */ nByte = nThread * ( sizeof(LloydsJob) + nElem * nK * sizeof_f32 + /* aSum[] */ nK * sizeof(int) /* aCount[] */ ); aJob = (LloydsJob*)sqlite3_malloc64(nByte); if( aJob==0 ) return SQLITE_NOMEM; memset(aJob, 0, sizeof(LloydsJob) * nThread); pCsr = (int*)&aJob[nThread]; /* Initialize each job object in the array */ for(ii=0; iinK = nK; pJob->aCentroid = aCode; pJob->aSum = (float*)pCsr; pCsr += (nK * nElem); pJob->aCount = pCsr; pCsr += nK; } aCount = aJob[0].aCount; aSum = aJob[0].aSum; #ifdef VEC1_USE_TRANSPOSITION /* If this build uses transposed arrays of centroids, allocate space ** for the array here, and give each job a pointer to it. */ aJob[0].aTrans = (float*)sqlite3_malloc64( sizeof_f32 * (nElem * nK) ); if( aJob[0].aTrans==0 ){ rc = SQLITE_NOMEM; goto vec1_kmeans_out; } for(ii=1; iinCompletedWork += VEC1_TRAINING_WORK_COARSE1; } /* Initial centroids have now been chosen. So do K-means iteration to ** improve the centroids. */ if( p ){ START_TRAINING_TIMER(p, VEC1_TRAINING_COARSE_KMEANS); vec1TrainLog(p, "k-means iterations for coarse quant"); } for(iIter=0; iItereDistance==VEC1_DISTANCE_COS ){ /* If this call is training a coarse quantizer for cosine distance, ** use "spherical k-means". This means that instead of just updating ** the centroid to be the average of all points assigned to it, we ** do that and then normalize the result - so that the centroid ** remains on the surface of the unit sphere. */ memcpy(aCode, aSum, sizeof_f32 * nK * nElem); for(ii=0; ii0 ){ int iElem; for(iElem=0; iElem0 && (fTotal / fPrevTotal)>=VEC1_PQ_KMEANS_STOPVAL ){ iIter = VEC1_PQ_KMEANS_NITER; } fPrevTotal = fTotal; } if( p ){ END_TRAINING_TIMER(p, VEC1_TRAINING_COARSE_KMEANS); p->nCompletedWork += VEC1_TRAINING_WORK_COARSE2; } vec1_kmeans_out: #ifdef VEC1_USE_TRANSPOSITION sqlite3_free( aJob[0].aTrans ); #endif sqlite3_free(aJob); return rc; } /* ** This function is called to train a coarse quantizer from within a ** vec1_train() invocation. Output array aCentroid[], which must be ** (p->tv.nElem * p->nBucket) elements in size, is packed with ** the p->nBucket centroids to use for coarse quantization before ** returning. */ static void vec1TrainCoarseQuant( Vec1TrainCtx *p, /* Training data + parameters */ Vec1JobQueue *pQueue, /* Job-queue, or NULL for single thread */ float *aCentroid /* OUT: Populate this array */ ){ if( p->rc==SQLITE_OK ){ int rc = vec1Ann1KMeans(p, &p->tv, pQueue, p->nBucket, aCentroid); assert( rc==SQLITE_OK || rc==SQLITE_NOMEM ); if( p->rc==SQLITE_OK && rc!=SQLITE_OK ){ p->rc = rc; sqlite3_result_error_nomem(p->pCtx); } } } static int vec1ConfigInt( int eType, i64 iVal, /* Proposed parameter value */ int nMin, /* Minimum allowed value */ int nMax, /* Maximum allowed value */ int bAllowZero, /* True if 0 is also allowed */ const char *zName, /* Name of parameter */ char **pzErr /* OUT: Error message */ ){ int rc = SQLITE_OK; if( eType!=SQLITE_INTEGER || (!(bAllowZero && iVal==0) && (iValnMax)) ){ *pzErr = sqlite3_mprintf( "vec1: %s must be set to an integer value between %d and %d%s", zName, nMin, nMax, (bAllowZero ? ", or 0" : "") ); rc = SQLITE_ERROR; } return rc; } static int vec1ConfigBool( int *pbOut, int eType, i64 iVal, const char *zName, char **pzErr ){ int rc = SQLITE_OK; if( eType==SQLITE_INTEGER && (iVal==0 || iVal==1) ){ *pbOut = (iVal!=0); }else{ *pzErr = sqlite3_mprintf("vec1: %s must be set to a boolean value", zName); rc = SQLITE_ERROR; } return rc; } static int vec1ConfigEnum( int *piOpt, /* OUT: Index of selected value */ const char *zVal, /* Candidate value */ const char **azOpt, /* Array of options */ const char *zName, /* Name of parameter */ char **pzErr /* OUT: Error message */ ){ int ii; char *zErr = 0; for(ii=0; azOpt[ii]; ii++){ if( sqlite3_stricmp(zVal, azOpt[ii])==0 ){ *piOpt = ii; return SQLITE_OK; } } zErr = sqlite3_mprintf("unrecognized %s '%s', should be one of", zName, zVal); for(ii=0; azOpt[ii]; ii++){ if( azOpt[ii+1] ){ zErr = sqlite3_mprintf("%z %s,", zErr, azOpt[ii]); }else{ zErr = sqlite3_mprintf("%z or %s", zErr, azOpt[ii]); } } *pzErr = zErr; return SQLITE_ERROR; } static int vec1ConfigDistance( int *peDist, /* OUT: VEC1_DISTANCE_L2 or DISTANCE_COS */ const char *zVal, /* Candidate value */ char **pzErr /* OUT: Error message */ ){ const char *azDist[] = {"l2", "cos", 0}; int iOpt = 0; int rc = vec1ConfigEnum(&iOpt, zVal, azDist, "distance", pzErr); assert( VEC1_DISTANCE_L2==1 ); assert( VEC1_DISTANCE_COS==2 ); *peDist = iOpt+1; return rc; } /* ** Parameter zVal points to a string that was the value for the "quantize" ** field of a training configuration object. If the string is a recognized ** value for this field, output parameter (*peQuant) is set to the ** corresponding VEC1_QUANTIZE_XXX value and SQLITE OK returned. ** ** Otherwise, if the value cannot be recognized, (*pzErr) is set to point ** to a buffer containing an English language error message and SQLITE_ERROR ** is returned. It is the responsibility of the caller to eventually free ** the (*pzErr) buffer using sqlite3_free(). The final value of (*peQuant) ** is undefined in this case. */ static int vec1ConfigQuantizer( int *peQuant, /* OUT: VEC1_QUANT_XXX value */ const char *zVal, /* Candidate value */ char **pzErr /* OUT: Error message */ ){ const char *azQuant[] = {"none", "pq", "opq", "bq", 0}; assert( sqlite3_stricmp("none", azQuant[VEC1_QUANTIZE_NONE])==0 ); assert( sqlite3_stricmp("pq", azQuant[VEC1_QUANTIZE_PQ])==0 ); assert( sqlite3_stricmp("opq", azQuant[VEC1_QUANTIZE_OPQ])==0 ); assert( sqlite3_stricmp("bq", azQuant[VEC1_QUANTIZE_BQ])==0 ); return vec1ConfigEnum(peQuant, zVal, azQuant, "quantizer", pzErr); } static int vec1Ann1TrainCfg( void *pCtx, const char *zOpt, int eType, i64 iVal, double fVal, const char *zVal, char **pz ){ int rc = SQLITE_OK; Vec1TrainCtx *p = (Vec1TrainCtx*)pCtx; const char *azOpt[] = { "distance", /* 0 */ "codesize", /* 1 */ "nbucket", /* 2 */ "quantizer", /* 3 */ "nthread", /* 4 */ "svd_verify", /* 5 */ "opq", /* 6 */ "progress", /* 7 */ "residual", /* 8 */ "wht", /* 9 */ "nopq_round", /* 10 */ "profile", /* 11 */ "index", /* 12 */ 0 }; int eOpt = -1; UNUSED_PARAMETER(fVal); rc = vec1ConfigEnum(&eOpt, zOpt, azOpt, "option", pz); if( rc==SQLITE_OK ){ switch( eOpt ){ case 0: assert( sqlite3_stricmp("distance", zOpt)==0 ); rc = vec1ConfigDistance(&p->eDistance, zVal, pz); break; case 1: assert( sqlite3_stricmp("codesize", zOpt)==0 ); rc = vec1ConfigInt(eType, iVal, VEC1_MIN_CODESIZE, VEC1_MAX_CODESIZE, 1, zOpt, pz ); p->nCodesize = (int)iVal; break; case 2: assert( sqlite3_stricmp("nbucket", zOpt)==0 ); rc = vec1ConfigInt(eType, iVal, VEC1_MIN_BUCKET, VEC1_MAX_BUCKET, 1, zOpt, pz ); p->nBucket = (int)iVal; break; case 3: assert( sqlite3_stricmp("quantizer", zOpt)==0 ); rc = vec1ConfigQuantizer(&p->eQuant, zVal, pz); break; case 4: assert( sqlite3_stricmp("nthread", zOpt)==0 ); rc = vec1ConfigInt(eType, iVal, 1, VEC1_MAX_NTHREAD, 0, zOpt, pz); #if VEC1_THREADS p->nThread = (int)iVal; #endif break; case 5: assert( sqlite3_stricmp("svd_verify", zOpt)==0 ); rc = vec1ConfigBool(&p->bSvdVerify, eType, iVal, zOpt, pz); break; case 6: assert( sqlite3_stricmp("opq", zOpt)==0 ); rc = vec1ConfigBool(&p->bOpq, eType, iVal, zOpt, pz); break; case 7: assert( sqlite3_stricmp("progress", zOpt)==0 ); sqlite3_free(p->zLogFunction); p->zLogFunction = sqlite3_mprintf("%s", zVal); if( p->zLogFunction==0 ) rc = SQLITE_NOMEM; break; case 8: assert( sqlite3_stricmp("residual", zOpt)==0 ); rc = vec1ConfigBool(&p->bResidual, eType, iVal, zOpt, pz); break; case 9: assert( sqlite3_stricmp("wht", zOpt)==0 ); rc = vec1ConfigBool(&p->bWHT, eType, iVal, zOpt, pz); break; case 10: assert( sqlite3_stricmp("nopq_round", zOpt)==0 ); rc = vec1ConfigInt(eType, iVal, VEC1_MIN_NOPQ_ROUND, VEC1_MAX_NOPQ_ROUND, 0, zOpt, pz ); p->nOpqRound = (int)iVal; break; case 11: assert( sqlite3_stricmp("profile", zOpt)==0 ); sqlite3_free(p->zProfileFunction); p->zProfileFunction = sqlite3_mprintf("%s", zVal); if( p->zProfileFunction==0 ) rc = SQLITE_NOMEM; break; default: assert( sqlite3_stricmp("index", zOpt)==0 ); assert( eOpt==12 ); /* index */ if( sqlite3_stricmp("trained", zVal) ){ *pz = sqlite3_mprintf( "unrecognized index '%s', should be 'trained'", zVal ); rc = SQLITE_ERROR; } break; } } return rc; } #define VEC1_INDEX_NONE 1 #define VEC1_INDEX_FLAT 2 typedef struct Vec1FlatIndex Vec1FlatIndex; struct Vec1FlatIndex { int eDistance; int eIndex; }; static int vec1FlatIndexCfg( void *pCtx, const char *zOpt, int eType, i64 iVal, double fVal, const char *zVal, char **pz ){ int rc = SQLITE_OK; Vec1FlatIndex *p = (Vec1FlatIndex*)pCtx; UNUSED_PARAMETER(eType); UNUSED_PARAMETER(iVal); UNUSED_PARAMETER(fVal); if( sqlite3_stricmp("distance", zOpt)==0 ){ rc = vec1ConfigDistance(&p->eDistance, zVal, pz); }else if( sqlite3_stricmp("index", zOpt)==0 ){ const char *azIdx[] = {"none", "flat", 0}; int iOpt = 0; rc = vec1ConfigEnum(&iOpt, zVal, azIdx, "index", pz); assert( VEC1_INDEX_NONE==1 ); assert( VEC1_INDEX_FLAT==2 ); p->eIndex = iOpt+1; }else{ *pz = sqlite3_mprintf("unknown parameter: %s", zOpt); rc = SQLITE_ERROR; } return rc; } /* ** aVec[] is a vector nElem elements in size. If any of these elements is ** non-finite - either NaN or an Infinity value - return SQLITE_ERROR. ** Return SQLITE_OK if all elements are finite. */ static int vec1VectorIsNonFinite(const float *aVec, int nElem){ static const u32 MASK = 0x7F800000; int ii = 0; #if defined(VEC1_HAVE_AVX2) const __m256i exp_mask = _mm256_set1_epi32(MASK); for(; ii<=(nElem-8); ii+=8){ __m256i bits = _mm256_loadu_si256((__m256i*)&aVec[ii]); __m256i exp_bits = _mm256_and_si256(bits, exp_mask); __m256i nonfinite = _mm256_cmpeq_epi32(exp_bits, exp_mask); if( _mm256_movemask_ps(_mm256_castsi256_ps(nonfinite)) ){ goto error_out; } } #endif #if defined(VEC1_HAVE_NEON) const uint32x4_t exp_mask = vdupq_n_u32(MASK); for(; ii<=(nElem-4); ii+=4){ uint32x4_t bits = vld1q_u32((u32*)&aVec[ii]); uint32x4_t exp_bits = vandq_u32(bits, exp_mask); uint32x4_t nonfinite = vceqq_u32(exp_bits, exp_mask); if( vmaxvq_u32(nonfinite) ){ goto error_out; } } #endif for(; iitv.nElem!=0 && p->tv.nElem!=(int)(n/sizeof_f32)) ){ sqlite3_result_error(pCtx, "vec1_train: bad argument", -1); return; } if( vec1VectorIsNonFinite((const float*)a, (n/sizeof_f32)) ){ sqlite3_result_error(pCtx, "vec1_train: non-finite value not allowed in vector", -1 ); return; } /* If this is the first call to xStep() for this aggregate, initialize ** the various "training parameters" fields of Vec1TrainCtx. */ if( p->tv.nElem==0 ){ p->tv.nElem = n/sizeof_f32; p->tv.nVecPerChunk = (VEC1_TRAINING_SZCHUNK / (p->tv.nElem*sizeof_f32)); /* Configure default values */ p->eDistance = VEC1_TRAINING_DEFAULT_DISTANCE; p->nBucket = VEC1_TRAINING_DEFAULT_NBUCKET; p->nThread = pList->nThread; p->nOpqRound = VEC1_TRAINING_DEFAULT_NOPQ_ROUND; p->bSvdVerify = VEC1_TRAINING_DEFAULT_SVD_VERIFY; p->bResidual = VEC1_TRAINING_DEFAULT_RESIDUAL; p->eQuant = -1; p->nCodesize = -1; p->bWHT = -1; p->bOpq = -1; /* If a configuration object was passed, update the Vec1TrainCtx with ** its field values now. */ if( nArg==2 ){ sqlite3 *db = sqlite3_context_db_handle(pCtx); const char *zCfg = (const char*)sqlite3_value_text(aArg[1]); char *zErr = 0; int rc = vec1ParseJsonConfig(db, zCfg, vec1Ann1TrainCfg, (void*)p, &zErr); if( rc ){ if( zErr!=0 ){ vec1ResultErrorF(pCtx, "%z", zErr); }else{ sqlite3_result_error_code(pCtx, rc); } return; } } /* If no quantizer was specified, select one automatically now. */ if( p->eQuant<0 ){ if( p->nCodesize<=0 ){ p->eQuant = VEC1_QUANTIZE_NONE; }else if( p->bOpq>0 ){ p->eQuant = VEC1_QUANTIZE_OPQ; }else{ p->eQuant = VEC1_QUANTIZE_PQ; } } /* If no "wht" value was explicitly specified, enable the Walsh-Hadamard ** Transform for binary quantizers. Disable it for other quantizers. */ if( p->bWHT<0 ) p->bWHT = (p->eQuant==VEC1_QUANTIZE_BQ); /* If no value was specified for "codesize", choose a default. This ** is 32 for PQ or OPQ, 0 for "none" quantizer, and (nElem+7)/8 for BQ. */ if( p->eQuant==VEC1_QUANTIZE_NONE ){ p->nCodesize = 0; p->bResidual = 0; }else if( p->eQuant==VEC1_QUANTIZE_BQ ){ if( p->nCodesize<=0 || p->nCodesize>=((p->tv.nElem+7)/8) ){ /* No explicit codesize. Or an explicit codesize large enough to ** use 1 bit per dimension. So do that - use 1 bit per dimension. */ p->nCodesize = (p->tv.nElem + 7) / 8; }else{ /* Explicit codesize too small for 1 bit per dimension. So use 1 bit ** for each two dimensions. */ p->nCodesize = (p->tv.nElem + 15) / 16; } }else if( p->nCodesize<=0 ){ if( p->eQuant==VEC1_QUANTIZE_PQ ){ p->nCodesize = (p->tv.nElem + 7) / 8; }else{ assert( p->eQuant==VEC1_QUANTIZE_OPQ ); p->nCodesize = 32; } } if( p->eQuant==VEC1_QUANTIZE_PQ || p->eQuant==VEC1_QUANTIZE_OPQ ){ /* Usually, tv.nElem will be an integer multiple of nCodebook. In ** this case set nCodeElem is just set to (tv.nElem / nCodebook) and ** nCodebook remains as configured. ** ** Or, if tv.nElem is not a multiple of nCodebook, set nCodeElem to ** the smallest value that allows us to cover the entire vector with ** nCodebook codebooks. Then reduce nCodebook to the minimum number ** of codebooks required to cover the vector with nCodeElem ** sub-vectors. */ p->nCodeElem = ((p->tv.nElem+p->nCodesize-1) / p->nCodesize); p->nCodesize = ((p->tv.nElem+p->nCodeElem-1) / p->nCodeElem); } } iChunk = (p->tv.nVec+1)/p->tv.nVecPerChunk; if( iChunk>=p->tv.nChunk ){ p->tv.aChunk[iChunk] = (float*)sqlite3_malloc(VEC1_TRAINING_SZCHUNK); if( p->tv.aChunk[iChunk]==0 ){ sqlite3_result_error_nomem(pCtx); return; } p->tv.nChunk++; } /* Append a copy of the vector to Vec1TrainCtx.aVec[]. */ memcpy(vec1TrainingVector(&p->tv, p->tv.nVec), sqlite3_value_blob(aArg[0]),n); p->tv.nVec++; } /* ** Normalize all the training vectors. */ static void vec1TrainNormalizeAll(Vec1TrainCtx *p){ int ii; for(ii=0; iitv.nVec; ii++){ vec1NormalizeVector(vec1TrainingVector(&p->tv, ii), p->tv.nElem); } } static void vec1FreeVectors(Vec1TrainVectors *pFree){ if( pFree ){ int ii; for(ii=0; iinChunk; ii++){ sqlite3_free(pFree->aChunk[ii]); } sqlite3_free(pFree); } } /* ** An instance of the following structure is allocated for each PQ codebook ** to train. Each codebook can then be trained in a separate thread. */ typedef struct Vec1CodebookJob Vec1CodebookJob; struct Vec1CodebookJob { Vec1TrainCtx *p; Vec1TrainVectors *pTrain; int iOff; float *aCode; int rc; }; static void vec1CodebookWork(void *pCtx){ Vec1CodebookJob *pJob = (Vec1CodebookJob*)pCtx; Vec1TrainCtx *p = pJob->p; int nByte = sizeof(Vec1TrainVectors); Vec1TrainVectors *pVec = (Vec1TrainVectors*)vec1MallocZero(nByte); if( pVec==0 ){ pJob->rc = SQLITE_NOMEM; }else{ int ii; int nShort = 0; int nBytePerVec = p->nCodeElem * sizeof_f32; pVec->nElem = p->nCodeElem; pVec->nVec = pJob->pTrain->nVec; pVec->nVecPerChunk = (VEC1_TRAINING_SZCHUNK / nBytePerVec); pVec->nChunk = (pVec->nVec+pVec->nVecPerChunk-1) / pVec->nVecPerChunk; /* If the training vector size is not an integer multiple of the ** sub-vector size and this is the rightmost sub-vector (the one that ** will need to be padded with 0.0 values), set nShort to non-zero. */ if( (pJob->iOff + p->nCodeElem)>pJob->pTrain->nElem ){ nShort = pJob->pTrain->nElem - pJob->iOff; } /* Allocate chunks for the new Vec1TrainVectors structure. If nShort ** is set, also zero them out. */ for(ii=0; iinChunk; ii++){ /* Allocate space for this chunk. Either VEC1_TRAINING_SZCHUNK or ** enough space for all remaining sub-vectors, whichever is smaller. */ int nAlloc = MIN(VEC1_TRAINING_SZCHUNK, (pVec->nVec - (ii * pVec->nVecPerChunk)) * nBytePerVec ); pVec->aChunk[ii] = (float*)sqlite3_malloc(nAlloc); if( pVec->aChunk[ii]==0 ){ pJob->rc = SQLITE_NOMEM; break; } if( nShort>0 ){ memset(pVec->aChunk[ii], 0, nAlloc); } } if( pJob->rc==SQLITE_OK ){ int nCopy = (nShort ? nShort : pVec->nElem) * sizeof_f32; for(ii=0; iinVec; ii++){ float *aVec = vec1TrainingVector(pJob->pTrain, ii); memcpy(vec1TrainingVector(pVec, ii), &aVec[pJob->iOff], nCopy); } pJob->rc = vec1Ann1KMeans(0, pVec, 0, VEC1_PQ_CODEBOOK_SZ, pJob->aCode); } vec1FreeVectors(pVec); } } static int vec1CodebookFinal(void *pCtx, int rcin){ UNUSED_PARAMETER(rcin); return ((Vec1CodebookJob*)pCtx)->rc; } static void vec1TrainPQCodebooks( Vec1TrainCtx *p, Vec1TrainVectors *pVec, Vec1JobQueue *pQueue, float *aBook ){ if( p->rc==SQLITE_OK ){ Vec1CodebookJob *aJob = 0; aJob = vec1MallocZero(sizeof(Vec1CodebookJob) * p->nCodesize); if( aJob ){ int ii; int rc = SQLITE_OK; for(ii=0; rc==SQLITE_OK && iinCodesize; ii++){ Vec1CodebookJob *pJob = &aJob[ii]; pJob->p = p; pJob->iOff = ii * p->nCodeElem; pJob->aCode = &aBook[ii * p->nCodeElem * VEC1_PQ_CODEBOOK_SZ]; pJob->rc = SQLITE_OK; pJob->pTrain = pVec; rc = vec1JobQueueAddJob( pQueue, vec1CodebookWork, vec1CodebookFinal, pJob ); } p->rc = vec1JobQueueFinishJobs(pQueue, rc); }else{ p->rc = SQLITE_NOMEM; } if( p->rc!=SQLITE_OK ){ sqlite3_result_error_code(p->pCtx, p->rc); } sqlite3_free(aJob); } } static void vec1TransposeMatrix(double *aMatrix, int nElem) { for (int i = 0; i < nElem - 1; i++) { for (int j = i + 1; j < nElem; j++) { double tmp = aMatrix[i * nElem + j]; aMatrix[i * nElem + j] = aMatrix[j * nElem + i]; aMatrix[j * nElem + i] = tmp; } } } static int vec1JacobiDoOnePair( int p, int q, double *A, int d, double *Vt, double fTol ){ double alpha = 0.0, beta = 0.0, gamma = 0.0; int r; double *Ap = A + p*d; double *Aq = A + q*d; assert( p=fTol*sqrt(alpha*beta) ){ double zeta = (beta - alpha) / (2.0 * gamma); double t, c, s; if (zeta >= 0.0){ t = 1.0 / ( zeta + sqrt(1.0 + zeta * zeta)); }else{ t = -1.0 / (-zeta + sqrt(1.0 + zeta * zeta)); } /* Damping factor apparently sometimes required by parallel Jacobi. ** Wait to see if we have a problem first I suppose... */ /* t = t*0.5; */ c = 1.0 / sqrt(1.0 + t * t); s = -c * t; /* Rotate columns p and q of A */ r = 0; #ifdef VEC1_HAVE_AVX2 __m256d vc = _mm256_set1_pd(c); __m256d vs = _mm256_set1_pd(s); __m256d vms = _mm256_set1_pd(-s); for(; rnThread + (p->nThread==1); int nBlk = nJob*2; int nByte = nJob * sizeof(Vec1JacobiJob); Vec1JacobiJob *aJob = (Vec1JacobiJob*)vec1MallocZero(nByte); *pnJob = 0; if( aJob==0 ){ p->rc = SQLITE_NOMEM; sqlite3_result_error_nomem(p->pCtx); }else{ int ii; int nColPerBlk = (nElem + nBlk-1)/ nBlk; for(ii=0; iiA = A; pJob->nElem = nElem; pJob->Vt = Vt; pJob->fTol = fTol; pJob->iJob = ii; pJob->nColPerBlk = nColPerBlk; pJob->nBlk = nBlk; } *pnJob = nJob; *pnStep = nBlk-1; } return aJob; } static void vec1JacobiWork(void *pArg){ Vec1JacobiJob *pJob = (Vec1JacobiJob*)pArg; int iBlk1 = 0; int iBlk2 = 0; int p0, p1, q0, q1; int p; vec1RoundRobinPair(pJob->nBlk, pJob->r, pJob->iJob, &iBlk1, &iBlk2); if( iBlk1>iBlk2 ){ SWAP(int, iBlk1, iBlk2); } p0 = iBlk1 * pJob->nColPerBlk; q0 = iBlk2 * pJob->nColPerBlk; p1 = MIN(p0 + pJob->nColPerBlk, pJob->nElem); q1 = MIN(q0 + pJob->nColPerBlk, pJob->nElem); /* Do cross-block pairs */ for(p=p0; pnApply += vec1JacobiDoOnePair( p, q, pJob->A, pJob->nElem, pJob->Vt, pJob->fTol ); } } /* If this is round 0, also do intra-block pairs */ if( pJob->r==0 ){ for(p=p0; pnApply += vec1JacobiDoOnePair( p, q, pJob->A, pJob->nElem, pJob->Vt, pJob->fTol ); } } for(p=q0; pnApply += vec1JacobiDoOnePair( p, q, pJob->A, pJob->nElem, pJob->Vt, pJob->fTol ); } } } } /* ** This routine computes SVD of a column-major square matrix A (d x d): ** ** A = U * diag(S) * Vt ** ** Parameters: ** A - input [d*d], column-major. MODIFIED BY THIS ROUTINE ** d - matrix dimension ** U - output [d*d], column-major. Columns = left singular vectors. ** S - output [d]. Singular values, not sorted. ** Vt - output [d*d], column-major storage of V^T. ** Row j of V^T (right singular vector j) occupies ** column j in memory: elements Vt[j*d .. j*d+d-1]. ** nMaxIter - max sweeps ** fTol - convergence tolerance ** ** Returns number of sweeps performed. **/ static void vec1JacobiSVD( Vec1TrainCtx *pTrain, Vec1JobQueue *pQueue, double *A, int d, double *U, double *S, double *Vt, int nMaxIter, /* Maximum number of sweeps */ double fTol ){ int j = 0; /* Iterator */ int nSweep = 0; /* Number of sweeps performed so far */ int nStep = 0; /* Number of steps per sweep */ Vec1JacobiJob *aJob = 0; int nJob = 0; assert( nMaxIter>0 && fTol>0.0 ); aJob = vec1JacobiAlloc(pTrain, A, d, Vt, fTol, &nJob, &nStep); if( aJob==0 ) return; /* Vt is initialised to an identity matrix. */ memset(Vt, 0, d * d * sizeof(double)); for(j=0; j0 ) break; } if( j==nJob ){ nSweep = nMaxIter; } } /* Vt was accumulated in row-major format. Flip it back to column-major */ vec1TransposeMatrix(Vt, d); /* ** Columns of A are now orthogonal. The singular value j is the norm ** of column j of A. Column j of U is column j of A, normalised. */ for(j=0; jfTol ){ for (r=0; rthreshold || fOrthVT>threshold || fReconstruction>threshold ){ vec1ResultErrorF(pCtx, "vec1: SVD error - " "fOrthU=%f, fOrthVT=%f, fReconstruction=%f", fOrthU, fOrthVT, fReconstruction ); return SQLITE_ERROR; } return SQLITE_OK; } typedef struct Vec1CovarianceJob Vec1CovarianceJob; struct Vec1CovarianceJob { double *aM; /* output matrix - each job writes distinct rows */ float *aBook; /* codebook */ Vec1TrainVectors *pVec; int nCodeElem; /* Number of elements in each subspace */ int iSub; /* Our job to do this subspace */ double fTotalDist; /* private accumulator */ #ifdef VEC1_USE_TRANSPOSITION float *aTrans; /* Transposed version of this job's codebook */ #endif }; /* ** One job per sub-space. */ static Vec1CovarianceJob *vec1CovarianceAlloc( Vec1TrainCtx *p, double *aM, Vec1TrainVectors *pVec, float *aBook, int *pnJob /* OUT: number of jobs */ ){ int nByte = sizeof(Vec1CovarianceJob) * p->nCodesize; Vec1CovarianceJob *aJob; #ifdef VEC1_USE_TRANSPOSITION /* Add space for each job to create a transposed version of its codebook */ nByte += sizeof_f32 * p->nCodeElem * VEC1_PQ_CODEBOOK_SZ * p->nCodesize; #endif aJob = (Vec1CovarianceJob*)vec1MallocZero(nByte); assert( p->rc==SQLITE_OK ); *pnJob = 0; if( aJob ){ int ii; #ifdef VEC1_USE_TRANSPOSITION float *aTrans = (float*)&aJob[p->nCodesize]; for(ii=0; iinCodesize; ii++){ aJob[ii].aTrans = &aTrans[ii * p->nCodeElem * VEC1_PQ_CODEBOOK_SZ]; } #endif for(ii=0; iinCodesize; ii++){ Vec1CovarianceJob *pJob = &aJob[ii]; pJob->aM = aM; pJob->aBook = aBook; pJob->pVec = pVec; pJob->nCodeElem = p->nCodeElem; pJob->iSub = ii; } *pnJob = p->nCodesize; }else{ sqlite3_result_error_nomem(p->pCtx); p->rc = SQLITE_NOMEM; } return aJob; } #if defined(VEC1_HAVE_AVX2) /* ** Convert each element of the 8-float vector fVec to a double, and add ** each scalar value to the corresponding aOut[] element. */ static void vec1AddToDoubleArrayAVX2(double *aOut, __m256 fVec){ __m256d lo = _mm256_cvtps_pd(_mm256_castps256_ps128(fVec) ); __m256d hi = _mm256_cvtps_pd(_mm256_extractf128_ps(fVec, 1)); __m256d lo1 = LOADU64(&aOut[0]); __m256d hi1 = LOADU64(&aOut[4]); _mm256_storeu_pd(&aOut[0], _mm256_add_pd(lo, lo1)); _mm256_storeu_pd(&aOut[4], _mm256_add_pd(hi, hi1)); } #endif #if defined(VEC1_HAVE_NEON) /* ** Convert each element of the 4-float vector fVec to a double, and add ** each scalar value to the corresponding aOut[] element. */ static void vec1AddToDoubleArrayNEON(double *aOut, float32x4_t fVec){ float64x2_t lo = vcvt_f64_f32(vget_low_f32(fVec)); float64x2_t hi = vcvt_high_f64_f32(fVec); float64x2_t lo1 = vld1q_f64(&aOut[0]); float64x2_t hi1 = vld1q_f64(&aOut[2]); vst1q_f64(&aOut[0], vaddq_f64(lo, lo1)); vst1q_f64(&aOut[2], vaddq_f64(hi, hi1)); } #endif /* ** The number of vectors handled by each pass in vec1CovarianceWork(). */ #define VEC1_COVAR_NVEC (VEC1_MULTIMATCH_NVEC * VEC1_COVAR_MULTIMATCH_MULT) /* ** For each vv in (0..VEC1_COVAR_NVEC-1), and each ii in ** (0..nElem-1), do the following: ** ** aOut[ii] = aOut[ii] + SUM(aScalar[vv] * aIn[vv][ii]) */ static void vec1CovarianceAccumulateN( double *aOut, const float *aIn[VEC1_COVAR_NVEC], const float aScalar[VEC1_COVAR_NVEC], int nElem ){ int ii = 0; int vv; #ifdef VEC1_HAVE_AVX2 #define VEC1_COVAR_NACC 8 __m256 vs[VEC1_COVAR_NVEC]; for(vv=0; vviSub*pJob->nCodeElem; float *aCode = &pJob->aBook[iSubOff * VEC1_PQ_CODEBOOK_SZ]; int ii, kk; int nVec = pJob->pVec->nVec; int nElem = pJob->pVec->nElem; int nKK = MIN(pJob->nCodeElem, nElem - iSubOff); #if defined(VEC1_USE_TRANSPOSITION) /* Populate this job's transposed copy of its slice of the codebook. */ vec1CodebookTranspose( aCode, pJob->nCodeElem, VEC1_NTRANSPOSE_WIDTH, VEC1_PQ_CODEBOOK_SZ, pJob->aTrans ); #endif /* Loop through the training vectors that are our responsibility, ** VEC1_COVAR_NVEC at a time. Each iteration of this outer loop ** compares VEC1_COVAR_NVEC sub-vectors to all candidate codebook ** entries (VEC1_MULTIMATCH_NVEC at a time, as that is the width the ** best-match routines operate on), then updates pJob->aM[] with the ** results. */ ii = 0; for(; ii<=(nVec-VEC1_COVAR_NVEC); ii+=VEC1_COVAR_NVEC){ int vv, iBatch; const float *aVec[VEC1_COVAR_NVEC]; const float *aSub[VEC1_COVAR_NVEC]; int aBest[VEC1_COVAR_NVEC]; const float *aSubHat[VEC1_COVAR_NVEC]; for(vv=0; vvpVec, ii+vv); aSub[vv] = &aVec[vv][iSubOff]; } for(iBatch=0; iBatchnCodeElem, aCode, pJob->aTrans, VEC1_PQ_CODEBOOK_SZ, &aSub[off], &aBest[off], &pJob->fTotalDist ); } for(vv=0; vvnCodeElem]; } /* For each row of pJob->aM[], gather the VEC1_COVAR_NVEC ** scalars that apply to it and stream through the whole row once, ** rather than once per training vector. */ for(kk=0; kkaM[(kk+iSubOff) * nElem], aVec, aScalar, nElem ); } } /* Scalar tail - handle training vectors not covered by the batched ** loop above because nVec is not a multiple of VEC1_COVAR_NVEC. */ for(; iipVec, ii); const float *aSub = &aVec[iSubOff]; int iBest = vec1BestMatchSimple( aCode, VEC1_PQ_CODEBOOK_SZ, aSub, pJob->nCodeElem, &pJob->fTotalDist ); float *aSubHat = &aCode[iBest * pJob->nCodeElem]; for(kk=0; kkaM[(kk+iSubOff) * nElem]; int iVec; for(iVec=0; iVecnVec vectors, each p->nElem elements each. */ static void vec1PqFindRotation( Vec1TrainCtx *p, Vec1JobQueue *pQueue, Vec1TrainVectors *pTrain, /* Rotated vectors to train on */ float *aBook, /* Current codebook */ float *aRotation /* OUT: best rotation */ ){ if( p->rc==SQLITE_OK ){ double *aM = 0; double *aU = 0; double *aVT = 0; double *aS = 0; double *aW = 0; int ii; int nElem2 = (p->tv.nElem * p->tv.nElem); Vec1CovarianceJob *aJob = 0; int nJob = 0; /* Allocate space for 4 nElem*nElem matrices - W, M, U and VT. And one ** nElem vector - S. */ aM = (double*)vec1MallocZero( 4 * nElem2 * sizeof(double) + p->tv.nElem * sizeof(double) ); if( aM==0 ){ p->rc = SQLITE_NOMEM; sqlite3_result_error_nomem(p->pCtx); return; } aVT = &aM[nElem2]; aU = &aVT[nElem2]; aW = &aU[nElem2]; aS = &aW[nElem2]; /* Step 1. Calculate the cross-covariance matrix M between training set X ** and the quantized version of the training set X^. i.e. the sum of the ** following for all x in X: ** ** xT . x^ ** ** (x1) (x1*x1^, x1*x2^, x1*x3^) ** (x2) . (x1^, x2^, x3^) == (x2*x1^, x2*x2^, x2*x3^) ** (x3) (x3*x1^, x3*x2^, x3*x3^) ** ** Because the covariance matrix is going to LAPACK, it has to be in ** column-major order. So aM[1]==(x2*x1^). */ START_TRAINING_TIMER(p, VEC1_TRAINING_ROT_COVARIANCE); aJob = vec1CovarianceAlloc(p, aM, pTrain, aBook, &nJob); if( aJob ){ for(ii=0; iitv.nElem, aU, aS, aVT, 50, 1e-14); END_TRAINING_TIMER(p, VEC1_TRAINING_ROT_JACOBI); vec1MatrixMultiply(aRotation, aU, aVT, p->tv.nElem); if( p->rc==SQLITE_OK && p->bSvdVerify ){ p->rc = vec1SVDSelfTest(p->pCtx, aM, aU, aS, aVT, p->tv.nElem); } sqlite3_free(aM); } } typedef struct Vec1RotationJob Vec1RotationJob; struct Vec1RotationJob { Vec1TrainVectors *pVec; /* Input vectors */ Vec1TrainVectors *pOut; /* Output vectors */ int iFirst; int iEof; const float *aRotation; /* nElem*nElem rotation matrix */ #ifdef VEC1_USE_TRANSPOSITION const float *aRotationNT; /* Transposed rotation matrix */ #endif }; static Vec1RotationJob *vec1RotationAlloc( Vec1TrainCtx *p, const float *aRotation, const float *aRotationNT, Vec1TrainVectors *pOut /* Store rotated vectors here */ ){ int nByte = p->nThread * sizeof(Vec1RotationJob); Vec1RotationJob *aJob = vec1MallocZero(nByte); if( aJob ){ int ii; for(ii=0; iinThread; ii++){ Vec1RotationJob *pJob = &aJob[ii]; ASSIGN_VECTORS_TO_JOB(&p->tv, pJob, ii, p->nThread); pJob->aRotation = aRotation; pJob->pOut = pOut; #ifdef VEC1_USE_TRANSPOSITION pJob->aRotationNT = aRotationNT; #else (void)aRotationNT; #endif } } return aJob; } #if defined(VEC1_USE_TRANSPOSITION) static void vec1RotateVectorTransposeN( float *aOut[VEC1_MULTIMATCH_NVEC], const float *aIn[VEC1_MULTIMATCH_NVEC], int nElem, const float *aRotation, const float *aRotationNT ){ int vv; int d; int ii; /* ii is the first output element in the VEC1_MULTIMATCH_NVEC output ** vectors. Each iteration of this outer loop populates 8 elements ** of each of them */ for(ii=0; ii<=(nElem-8); ii+=8){ const float *aNT = &aRotationNT[(ii * nElem)]; __m256 aAcc[VEC1_MULTIMATCH_NVEC]; for(vv=0; vvpVec->nElem; int ii = pJob->iFirst; for(; ii<=(pJob->iEof-VEC1_MULTIMATCH_NVEC); ii+=VEC1_MULTIMATCH_NVEC){ const float *aIn[VEC1_MULTIMATCH_NVEC]; float *aOut[VEC1_MULTIMATCH_NVEC]; int vv; for(vv=0; vvpVec, vv+ii); aOut[vv] = vec1TrainingVector(pJob->pOut, vv+ii); } #ifdef VEC1_USE_TRANSPOSITION vec1RotateVectorTransposeN( aOut, aIn, nElem, pJob->aRotation, pJob->aRotationNT ); #else vec1RotateVectorN(aOut, aIn, nElem, pJob->aRotation); #endif } for(; iiiEof; ii++){ const float *pIn = vec1TrainingVector(pJob->pVec, ii); float *pOut = vec1TrainingVector(pJob->pOut, ii); vec1RotateVector(nElem, pJob->aRotation, pIn, pOut); } } /* ** Create and return a deep copy of the training vectors passed as the ** only argument. */ static Vec1TrainVectors *vec1CopyVectors(Vec1TrainVectors *pIn){ Vec1TrainVectors *pRet = 0; int ii; pRet = (Vec1TrainVectors*)vec1MallocZero(sizeof(Vec1TrainVectors)); if( pRet==0 ) return pRet; pRet->nElem = pIn->nElem; pRet->nVec = pIn->nVec; pRet->nChunk = pIn->nChunk; pRet->nVecPerChunk = pIn->nVecPerChunk; for(ii=0; iinChunk; ii++){ int nByte = pRet->nVecPerChunk * pIn->nElem * sizeof_f32; pRet->aChunk[ii] = (float*)sqlite3_malloc(VEC1_TRAINING_SZCHUNK); if( pRet->aChunk[ii]==0 ){ vec1FreeVectors(pRet); return 0; } if( ii==pIn->nChunk-1 ){ nByte = (pRet->nVec - ii*pRet->nVecPerChunk) * pRet->nElem*sizeof_f32; } assert( nByte<=VEC1_TRAINING_SZCHUNK ); memcpy(pRet->aChunk[ii], pIn->aChunk[ii], nByte); } return pRet; } static void vec1TrainRotation( Vec1TrainCtx *p, Vec1JobQueue *pQueue, float *aBook, float *aRotation ){ const int nElem = p->tv.nElem; const int nMatrixByte = sizeof_f32 * nElem * nElem; int iRound = 0; /* Current OPQ round */ int ii; float *aRot = (float*)sqlite3_malloc(nMatrixByte * 2); float *aRot2 = &aRot[nElem*nElem]; Vec1TrainVectors *pTrain = vec1CopyVectors(&p->tv); Vec1RotationJob *aJob = 0; aJob = vec1RotationAlloc(p, aRotation, aRot2, pTrain); if( pTrain==0 || aRot==0 || aJob==0 ){ sqlite3_result_error_nomem(p->pCtx); p->rc = SQLITE_NOMEM; } for(iRound=0; iRoundnOpqRound; iRound++){ vec1TrainLog(p, "OPQ round %d/%d", iRound+1, p->nOpqRound); START_TRAINING_TIMER(p, VEC1_TRAINING_ROT_CODEBOOKS); vec1TrainPQCodebooks(p, pTrain, pQueue, aBook); END_TRAINING_TIMER(p, VEC1_TRAINING_ROT_CODEBOOKS); vec1PqFindRotation(p, pQueue, pTrain, aBook, aRot); if( p->rc==SQLITE_OK ){ if( iRound==0 ){ memcpy(aRotation, aRot, nMatrixByte); }else{ memcpy(aRot2, aRotation, nMatrixByte); vec1MatrixMultiplyF(aRotation, aRot2, aRot, nElem); } START_TRAINING_TIMER(p, VEC1_TRAINING_ROT_VECTORS); #ifdef VEC1_USE_TRANSPOSITION vec1CodebookTranspose(aRotation, nElem, VEC1_NTRANSPOSE_WIDTH, nElem, aRot2 ); #endif for(ii=0; iinThread; ii++){ vec1JobQueueAddJob(pQueue, vec1RotationWork, 0, &aJob[ii]); } vec1JobQueueFinishJobs(pQueue, SQLITE_OK); END_TRAINING_TIMER(p, VEC1_TRAINING_ROT_VECTORS); } p->nCompletedWork += VEC1_TRAINING_WORK_OPQ; } if( p->rc==SQLITE_OK ){ for(ii=0; iitv.nChunk; ii++){ SWAP(float*, p->tv.aChunk[ii], pTrain->aChunk[ii]); } } sqlite3_free(aJob); sqlite3_free(aRot); vec1FreeVectors(pTrain); } static void vec1TrainLogTimes(Vec1TrainCtx *p){ double fTotal = (double)p->aTime[VEC1_TRAINING_TOTAL]; if( p->zProfileFunction ){ struct Counter { const char *zName; int iTime; } aCounter[] = { { "rot_codebooks", VEC1_TRAINING_ROT_CODEBOOKS }, { "rot_covariance", VEC1_TRAINING_ROT_COVARIANCE }, { "rot_jacobi", VEC1_TRAINING_ROT_JACOBI }, { "rot_vectors", VEC1_TRAINING_ROT_VECTORS }, { "coarse_init", VEC1_TRAINING_COARSE_INIT }, { "coarse_lloyds", VEC1_TRAINING_COARSE_KMEANS }, { "residuals", VEC1_TRAINING_RESIDUALS }, { "codebooks", VEC1_TRAINING_CODEBOOKS } }; int icall; for(icall=0; icall<2; icall++){ int ii; char *zSql = 0; sqlite3_str *pSql = sqlite3_str_new(0); sqlite3_str_appendf(pSql, "SELECT \"%w\"('{", p->zProfileFunction); if( icall==0 ){ for(ii=0; ii0) ? ", " : ""), aCounter[ii].zName, p->aTime[ aCounter[ii].iTime ]*100.00 / fTotal ); } }else{ for(ii=0; iiaTime[ aCounter[ii].iTime ]; sqlite3_str_appendf(pSql, "%s\"%s\": ", ((ii>0) ? ", " : ""), aCounter[ii].zName ); if( val>(1000*1000*1000) ){ sqlite3_str_appendf(pSql, "\"%.2fG\"", (double)val / 1000000000.0); }else if( val>(10*1000*1000) ){ sqlite3_str_appendf(pSql, "\"%dM\"", (int)(val / (1000*1000))); }else if( val>(1*1000*1000) ){ sqlite3_str_appendf(pSql, "\"%.2fM\"", (double)val / 1000000.0); }else if( val>(10*1000) ){ sqlite3_str_appendf(pSql, "\"%dK\"", (int)(val / (1000))); }else if( val>(1000) ){ sqlite3_str_appendf(pSql, "\"%.2fK\"", (double)val / 1000.0); }else{ sqlite3_str_appendf(pSql, "\"%d\"", (int)val); } } } sqlite3_str_appendf(pSql, "}')"); zSql = sqlite3_str_finish(pSql); if( zSql==0 ){ sqlite3_result_error_nomem(p->pCtx); break; }else{ sqlite3 *db = sqlite3_context_db_handle(p->pCtx); char *zErrmsg = 0; int rc = sqlite3_exec(db, zSql, 0, 0, &zErrmsg); if( rc!=SQLITE_OK ){ sqlite3_result_error(p->pCtx, zErrmsg, -1); sqlite3_result_error_code(p->pCtx, rc); break; } sqlite3_free(zErrmsg); sqlite3_free(zSql); } } } } typedef struct Vec1ResidualJob Vec1ResidualJob; struct Vec1ResidualJob { /* Data to train on */ const Vec1TrainCtx *p; Vec1TrainVectors *pVec; int iFirst; int iEof; const float *aCentroid; int nCentroid; }; static void vec1TrainResidualWorker(void *pCtx){ Vec1ResidualJob *p = (Vec1ResidualJob*)pCtx; int ii; int nElem = p->pVec->nElem; for(ii=p->iFirst; iiiEof; ii++){ float *a = vec1TrainingVector(p->pVec, ii); int iBest = vec1BestMatchSimple(p->aCentroid, p->nCentroid, a, nElem, 0); vec1SubInPlace(a, &p->aCentroid[iBest * nElem], nElem); } } static void vec1TrainFindResiduals( Vec1TrainCtx *p, Vec1JobQueue *pQueue, const float *aCentroid ){ if( p->rc==SQLITE_OK ){ int ii; int nJob = p->nThread; Vec1ResidualJob *aJob = 0; int rc = SQLITE_OK; assert( nJob>=1 ); aJob = (Vec1ResidualJob*)vec1MallocZero(sizeof(Vec1ResidualJob) * nJob); if( aJob==0 ){ sqlite3_result_error_nomem(p->pCtx); p->rc = SQLITE_NOMEM; return; } for(ii=0; iitv, pJob, ii, nJob); pJob->p = p; pJob->aCentroid = aCentroid; pJob->nCentroid = p->nBucket; vec1JobQueueAddJob(pQueue, vec1TrainResidualWorker, 0, (void*)pJob); } vec1JobQueueFinishJobs(pQueue, rc); sqlite3_free(aJob); } } static void vec1TrainPrepareLog(Vec1TrainCtx *p){ if( p->zLogFunction ){ char *zSql = sqlite3_mprintf("SELECT %s(?, ?)", p->zLogFunction); if( zSql==0 ){ sqlite3_result_error_nomem(p->pCtx); p->rc = SQLITE_NOMEM; }else{ sqlite3 *db = sqlite3_context_db_handle(p->pCtx); p->rc = sqlite3_prepare_v2(db, zSql, -1, &p->pLogStmt, 0); sqlite3_free(zSql); if( p->rc==SQLITE_ERROR ){ sqlite3_result_error(p->pCtx, sqlite3_errmsg(db), -1); }else if( p->rc!=SQLITE_OK ){ sqlite3_result_error_code(p->pCtx, p->rc); } } } } static void vec1TrainWorkInit(Vec1TrainCtx *p){ int nTotal = 0; if( p->eQuant==VEC1_QUANTIZE_OPQ ){ nTotal += VEC1_TRAINING_WORK_OPQ * p->nOpqRound; } if( p->nBucket>0 ){ nTotal += VEC1_TRAINING_WORK_COARSE1 + VEC1_TRAINING_WORK_COARSE2; } if( p->eQuant==VEC1_QUANTIZE_PQ || p->eQuant==VEC1_QUANTIZE_OPQ ){ nTotal += VEC1_TRAINING_WORK_CODEBOOKS; } p->nTotalWork = (nTotal ? nTotal : 1); } /* ** Return true if n is a power of 2, or false otherwise. */ static int vec1IsPow2(int n) { return n > 0 && (n & (n-1)) == 0; } /* ** Apply Walsh-Hadamard Transform to n element vector x. n must ** be a power of 2. */ static void vec1WHT(float *x, int n){ assert( vec1IsPow2(n) ); for (int stride = 1; stride < n; stride <<= 1) { for (int i = 0; i < n; i += stride << 1) { for (int j = i; j < i + stride; j++) { float a = x[j]; float b = x[j + stride]; x[j] = a + b; x[j+stride] = a - b; } } } } /* ** Apply Walsh-Hadamard transform to nElem element vector aVec. */ static void vec1ApplyWHT(float *aVec, int nElem){ if (vec1IsPow2(nElem)) { vec1WHT(aVec, nElem); }else{ int p = 1; while( (p<<1)tv.nVec; ii++){ vec1ApplyWHT(vec1TrainingVector(&p->tv, ii), p->tv.nElem); } } /* ** The xFinal() method for the vec1_train() aggregate function. ** ** Output a trained model blob. */ static void vec1TrainFinal(sqlite3_context *pCtx){ Vec1TrainCtx *p = 0; float *aBook = 0; /* Code books */ float *aCentroid = 0; /* Centroids */ float *aRotation = 0; /* Rotation */ int nByte = 0; u8 *aByte = 0; float *pCsr = 0; Vec1JobQueue *pQueue = 0; u32 flags = 0; int ii; int bPQ = 0; /* Check that sufficient training vectors were provided. */ p = (Vec1TrainCtx*)sqlite3_aggregate_context(pCtx, sizeof(*p)); if( p==0 ) return; p->pCtx = pCtx; bPQ = (p->eQuant==VEC1_QUANTIZE_PQ || p->eQuant==VEC1_QUANTIZE_OPQ); if( p->tv.nVec==0 ){ vec1ResultErrorF(pCtx, "too few training vectors (have 0, require more than that)" ); goto train_final_out; } if( p->nBucket || bPQ ){ int nReq = p->nBucket * 4; if( bPQ ) nReq = MAX(VEC1_PQ_TRAINING_SET_MIN, nReq); if( p->tv.nVectv.nVec ); goto train_final_out; } } vec1TrainWorkInit(p); vec1TrainPrepareLog(p); vec1TrainLog(p, "vectors loaded, starting training"); if( p->rc!=SQLITE_OK ) goto train_final_out; START_TRAINING_TIMER(p, VEC1_TRAINING_TOTAL); /* If this is training for "cos" distance, not "l2", normalize all ** training vectors before proceding as usual. */ if( p->eDistance==VEC1_DISTANCE_COS ){ vec1TrainNormalizeAll(p); } /* Allocate space for the model. Space is required for (A) the header and ** coarse centroids, (B) the PQ codebooks and (C) the OPQ rotation ** matrix. */ nByte = VEC1_HEADER_SIZE + (sizeof_f32 * p->nBucket * p->tv.nElem); /* A */ if( bPQ ){ nByte += sizeof_f32*p->nCodesize*p->nCodeElem*VEC1_PQ_CODEBOOK_SZ; /* B */ } if( p->eQuant==VEC1_QUANTIZE_OPQ ){ nByte += sizeof_f32 * p->tv.nElem * p->tv.nElem; /* C */ } aByte = (u8*)vec1ContextMalloc(pCtx, nByte); if( aByte==0 ) goto train_final_out; pCsr = (float*)&aByte[VEC1_HEADER_SIZE]; if( bPQ ){ aBook = pCsr; pCsr += (p->nCodesize * p->nCodeElem * VEC1_PQ_CODEBOOK_SZ); } if( p->nBucket>0 ){ aCentroid = pCsr; pCsr += (p->nBucket * p->tv.nElem); } if( p->eQuant==VEC1_QUANTIZE_OPQ ){ aRotation = pCsr; pCsr += (p->tv.nElem * p->tv.nElem); } assert( (u8*)pCsr==&aByte[nByte] ); /* Allocate a jobs queue, if multi-threading is required. If it is not ** required, leave pQueue set to NULL. Things will proceed in a single ** thread in that case. */ #if VEC1_THREADS if( p->nThread>1 ){ pQueue = vec1JobQueueNew(p->nThread-1); if( pQueue==0 ){ sqlite3_result_error_nomem(pCtx); goto train_final_out; } } #endif /* Write the model header into the output buffer. */ flags = VEC1_MODEL_INDEX | ((p->nBucket>0 && p->bResidual) ? VEC1_MODEL_RESIDUAL : 0) | ((p->eQuant==VEC1_QUANTIZE_OPQ) ? VEC1_MODEL_ROTATE : 0) | (p->bWHT ? VEC1_MODEL_WHT : 0) | ((p->eQuant==VEC1_QUANTIZE_BQ) ? VEC1_MODEL_BITQUANT : 0); vec1HeaderWrite(aByte, flags, p->tv.nElem, p->nCodesize, p->nBucket, p->eDistance ); /* If an OPQ rotation is require, calculate one now */ if( p->eQuant==VEC1_QUANTIZE_OPQ ){ vec1TrainRotation(p, pQueue, aBook, aRotation); }else if( p->bWHT ){ vec1TrainApplyWHT(p); } if( p->nBucket>0 ){ vec1TrainCoarseQuant(p, pQueue, aCentroid); /* For each training vector, find the closest centroid. Then subtract ** the centroid from the training vector, to leave the residual vector. ** This is what the PQ step trains on. */ if( bPQ && p->bResidual ){ vec1TrainLog(p, "calculating residuals"); START_TRAINING_TIMER(p, VEC1_TRAINING_RESIDUALS); vec1TrainFindResiduals(p, pQueue, aCentroid); END_TRAINING_TIMER(p, VEC1_TRAINING_RESIDUALS); } } /* Now train the PQ codebooks, if required. Each codebook is trained in ** parallel, using a separate thread if threads were configured. */ if( bPQ ){ vec1TrainLog(p, "PQ codebook training"); START_TRAINING_TIMER(p, VEC1_TRAINING_CODEBOOKS); vec1TrainPQCodebooks(p, &p->tv, pQueue, aBook); p->nCompletedWork += VEC1_TRAINING_WORK_CODEBOOKS; END_TRAINING_TIMER(p, VEC1_TRAINING_CODEBOOKS); } /* Return the result blob. */ if( p->rc==SQLITE_OK ){ sqlite3_result_blob(pCtx, (void*)aByte, nByte, vec1SqliteFree); aByte = 0; } train_final_out: END_TRAINING_TIMER(p, VEC1_TRAINING_TOTAL); vec1TrainLog(p, "training complete"); vec1TrainLogTimes(p); sqlite3_finalize(p->pLogStmt); sqlite3_free(p->zLogFunction); sqlite3_free(p->zProfileFunction); for(ii=0; iitv.nChunk; ii++){ sqlite3_free(p->tv.aChunk[ii]); } vec1JobQueueFree(pQueue); sqlite3_free(aByte); } typedef struct Vec1Model Vec1Model; struct Vec1Model { Vec1ModelHeader hdr; const float *aModel; const float *aCentroid; const float *aRotation; int nCodeElem; float *aModelT; #ifdef VEC1_USE_TRANSPOSITION float *aCentroidNT; float *aModelNT; float *aRotationNT; #endif }; static int vec1DecodeModel( const u8 *aBlob, int nByte, Vec1Model *pMod, char **pzErr ){ const u8 *pCsr = aBlob; int nCodebook = 0; if( nBytehdr); pCsr += VEC1_HEADER_SIZE; assert( VEC1_HEADER_VERSION_BIN>VEC1_HEADER_VERSION ); if( pMod->hdr.iVersion!=VEC1_HEADER_VERSION && pMod->hdr.iVersion!=VEC1_HEADER_VERSION_BIN ){ if( pMod->hdr.iVersionhdr.iVersion / 1000), (pMod->hdr.iVersion % 1000) ); } return SQLITE_ERROR; } /* Check header fields are within the acceptable ranges. This also serves ** to ensure that the 32-bit arithmetic used to calculate offsets below ** does not overflow. */ if( pMod->hdr.nElem>VEC1_VECSIZE_MAX || pMod->hdr.nBucket>VEC1_MAX_BUCKET || pMod->hdr.nCodebook>pMod->hdr.nElem ){ *pzErr = sqlite3_mprintf("vec1: model is corrupt"); return SQLITE_ERROR; } nCodebook = pMod->hdr.nCodebook; if( nCodebook>0 ){ if( (pMod->hdr.flags & VEC1_MODEL_BITQUANT)==0 ){ pMod->nCodeElem = (pMod->hdr.nElem + nCodebook-1)/nCodebook; pMod->aModel = (float*)pCsr; pCsr += (nCodebook*pMod->nCodeElem*VEC1_PQ_CODEBOOK_SZ * sizeof_f32); }else{ if( ((pMod->hdr.nElem + 7)/8)==(u32)nCodebook ){ pMod->nCodeElem = 8; }else if( ((pMod->hdr.nElem + 15)/16)==(u32)nCodebook ){ pMod->nCodeElem = 16; }else{ *pzErr = sqlite3_mprintf("vec1: unrecognized bq codesize"); return SQLITE_ERROR; } } } if( pMod->hdr.nBucket>0 ){ pMod->aCentroid = (float*)pCsr; pCsr += (pMod->hdr.nElem * pMod->hdr.nBucket * sizeof_f32); } if( (pMod->hdr.flags & VEC1_MODEL_ROTATE) ){ pMod->aRotation = (float*)pCsr; pCsr += (pMod->hdr.nElem * pMod->hdr.nElem * sizeof_f32); } if( (pCsr - aBlob)!=nByte ){ *pzErr = sqlite3_mprintf( "vec1: model size mismatch: have %d, expected %d", nByte, (pCsr-aBlob) ); return SQLITE_ERROR; } return SQLITE_OK; } #if defined(VEC1_USE_TRANSPOSITION) static int vec1ModelAllocTCent(Vec1Model *pMod){ int rc = SQLITE_OK; if( pMod->aCentroidNT==0 && (pMod->hdr.nBucket>0 || pMod->aModel) ){ int nElem = pMod->hdr.nElem; int nByte = sizeof_f32 * nElem * pMod->hdr.nBucket; if( pMod->aModel ){ int nFloatPerCodebook = pMod->nCodeElem * pMod->hdr.nCodebook; nByte += (VEC1_PQ_CODEBOOK_SZ * nFloatPerCodebook * sizeof_f32); if( pMod->aRotation ){ nByte += (nElem * nElem) * sizeof_f32; } } pMod->aCentroidNT = (float*)sqlite3_malloc(nByte); if( pMod->aCentroidNT==0 ){ rc = SQLITE_NOMEM; }else{ assert( pMod->aCentroid || pMod->hdr.nBucket==0 ); vec1CodebookTranspose(pMod->aCentroid, nElem, VEC1_NTRANSPOSE_WIDTH, pMod->hdr.nBucket, pMod->aCentroidNT ); if( pMod->aModel ){ u32 M; int szCodebook = pMod->nCodeElem * VEC1_PQ_CODEBOOK_SZ; pMod->aModelNT = &pMod->aCentroidNT[nElem * pMod->hdr.nBucket]; for(M=0; Mhdr.nCodebook; M++){ vec1CodebookTranspose( &pMod->aModel[szCodebook*M], pMod->nCodeElem, VEC1_NTRANSPOSE_WIDTH, VEC1_PQ_CODEBOOK_SZ, &pMod->aModelNT[szCodebook*M] ); } if( pMod->aRotation ){ /* Create a transposed version of the rotation matrix. */ pMod->aRotationNT = &pMod->aModelNT[szCodebook * M]; vec1CodebookTranspose(pMod->aRotation, nElem, VEC1_NTRANSPOSE_WIDTH, nElem, pMod->aRotationNT ); } } } } return rc; } static void vec1ModelFreeTCent(Vec1Model *pMod){ sqlite3_free(pMod->aCentroidNT); pMod->aCentroidNT = 0; pMod->aModelNT = 0; pMod->aRotationNT = 0; } #else # define vec1ModelAllocTCent(pMod) SQLITE_OK # define vec1ModelFreeTCent(pMod) #endif /************************************************************************* ** Start of virtual table code */ /* %_IDX TABLE LIST FORMAT: ** ** Each list begins with 3 32-bit unsigned integers in big-endian format. ** As follows: ** ** bytes 0..3: Flags value. ** bytes 4..7: Total number of entries in list. ** bytes 8..11: Number of tombstone entries in list ** ** Following these is an array of rowid values - either unsigned 32-bit ** values or 64-bit signed values, depending on the flags set in the ** first field value of the list. In the 32-bit format, the tombstone ** value is 0xFFFFFFFF. For the 64-bit format, it is 0. Rowids are stored ** in big-endian format. ** ** There are two formats - one that uses 32-bit rowids, and another that ** uses 64-bit rowids. Format consists of one or more blocks. Each block ** stores 16 PQ codes: ** ** * 16 rowid values - either 16 * 32-bit unsigned or 16 * 64-bit ** signed. The only way to know which is to read the first/last ** column values from the %_idx table. ** ** * The lookup value for the first codebook for each of the 16 ** codes in the block. Followed by the lookup value for the second ** codebook for each of the 16 codes, and so on. ** ** A PQ code list always consists of an exact number of blocks. In the ** 32-bit rowid format, a rowid value of 0xFFFFFFFF indicates an invalid ** entry. In the 64-bit format, an invalid entry is one with a rowid value ** of 0. */ /* %_META TABLE LIST FORMAT: ** ** Meta table lists always begin with 2 32-bit unsigned integers in ** big-endian format: ** ** Bytes 0..3: 32-bit big-endian flags field. ** Bytes 4..7: 32-bit big-endian nEntry field. ** ** The flags field is set to a combination of VEC1_META_XXX flags. The ** nEntry fields is set to the number of entries in the list. The format ** of the rest of the list is different depending on the flags: ** ** VEC1_META_GENERIC: ** ** This is the fallback format used if none of the other formats can be ** used. It encodes each value separately as a varint type followed by ** encoded data. Specifically, for type values 0, 1 and 2: ** ** 0: NULL value. 0 byte payload. ** 1: Unsigned integer value in range [0..254]. 1 byte payload. ** 2: Integer value. 4 byte payload in range [-2147483647..+2147483647]. ** 3: Integer value. 8 byte payload. ** 4: Real value. 8 byte payload. ** ** Odd values larger than 2 are text values, with size in bytes of ** (type - 2)/2. Even values larger than 2 are blobs. ** ** VEC1_META_1BYTEINT: ** This format may be used if all values in the list are either NULL or ** integers between 0 and 254, inclusive. Each value is stored as ** a 1 byte unsigned value, with 255 (0xFF) representing NULL. ** ** VEC1_META_4BYTEINT: ** This format may be used if all values in the list are either NULL or ** integers between -2147483648 and +2147483646, inclusive. Each value is ** stored as a 4 byte two's complement value, with +2147483647 ** (0x7FFFFFFF) representing NULL. ** ** VEC1_META_REAL: ** This format may be used if all values in the list are real or NULL. ** Bit pattern 0x7FF8000000000001 is used for NULL. ** ** Flag VEC1_META_HASNULL is set if the list contains at least one NULL ** value. */ #define VEC1_META_GENERIC 0x01 #define VEC1_META_1BYTEINT 0x02 #define VEC1_META_4BYTEINT 0x04 #define VEC1_META_REAL 0x08 #define VEC1_META_HASNULL 0x10 #define VEC1_META_TYPEMASK ( \ VEC1_META_GENERIC | VEC1_META_1BYTEINT | VEC1_META_4BYTEINT | VEC1_META_REAL \ ) #define VEC1_META_SZHDR 8 #define VEC1_META_1BYTENULL 255 #define VEC1_META_REALNULL 0x7FF8000000000001 #define VEC1_META_4BYTEMIN (-2147483647-1) #define VEC1_META_4BYTEMAX +2147483646 #define VEC1_META_4BYTENULL +2147483647 /* When a meta-value array is loaded from disk, at least VEC1_META_PADDING ** extra bytes of memory are allocated. This is to allow readers to safely ** overread the record slightly if it is corrupt. */ #define VEC1_META_PADDING 20 typedef struct Vec1Tab Vec1Tab; typedef struct Vec1Csr Vec1Csr; /* ** Flags that may be part of the first 32-bit unsigned integer of a PQ list. */ #define VEC1_LIST_64BIT 0x01 /* Set for 64-bit rowids, clear for 32-bit */ #define VEC1_LIST_SORTED 0x02 /* True if rowid array is in sorted order */ /* Size of list header in byte */ #define VEC1_LIST_SZHDR (3*sizeof_u32) #define VEC1_TOMBSTONE_32 0xFFFFFFFF #define VEC1_TOMBSTONE_64 0 /* ** Fields in the virtual table: */ #define VEC1_COLUMN_CMD 0 #define VEC1_COLUMN_ARG 1 #define VEC1_COLUMN_DISTANCE 2 #define VEC1_COLUMN_VECTOR 3 /* Followed by Vec1Tab.nMeta meta-data columns */ /* ** MODEL (0): ** This integer value is set to 1 when a model is first provided to ** the table, and incremented each time the model is updated thereafter. ** ** BLOCKSIZE (1): ** The config entry for the target size (in bytes) for chunks stored in ** the %_idx table. ** ** FMTVERSION (5): ** This is an integer - the current format version. Currently this is ** always 1, but will be increased if the format changes. If a client ** finds a format it does not understand, it has to rebuild the index. */ #define VEC1_CONFIG_MODEL 0 #define VEC1_CONFIG_BLOCKSIZE 1 #define VEC1_CONFIG_NELEM 2 #define VEC1_CONFIG_BLOCKSIZE_MIN 3 #define VEC1_CONFIG_FMTVERSION 5 /* ** Return the shadow-schema used by the vec1_ann table named zTab. Parameter ** zDb must be the name of the database (e.g. "main", "temp" or the name ** of an attached database). */ static char *vec1ShadowSchema(const char *zDb, const char *zTab, int nMeta){ const char zFmt[] = /* Configuration table. This contains persistent configuration parameters, ** including the training data. Keys must be one of the VEC1_CONFIG_XXX ** values defined above. */ "CREATE TABLE %Q.'%q_config'(id INTEGER PRIMARY KEY, val ANY);" /* Base table. This contains the raw data as provided by the user. */ "CREATE TABLE %Q.'%q_base'(" "id INTEGER PRIMARY KEY, " "vector BLOB%z" ");" /* Index data */ "CREATE TABLE %Q.'%q_idx'(" "id INTEGER PRIMARY KEY, "/* Id of blob */ "bucket INTEGER, " /* Index of bucket for this blob (or NULL) */ "first INTEGER, " /* Smallest rowid in blob */ "last INTEGER, " /* Largest rowid in blob */ "val BLOB" /* Blob containing rowids + PQ codes */ ");" "CREATE INDEX %Q.'%q_idx_idx' ON '%q_idx'(bucket, first, last);" /* Training data (codebooks and centroids). This is stored as a single ** blob with id=1, in the same format as returned by vec1_train(). */ "CREATE TABLE %Q.'%q_model'(id INTEGER PRIMARY KEY, val BLOB);" /* Meta table. Contains arrays of meta values stored in parallel with ** arrays in the %_idx table. For column iMeta and %_idx array id=iId, ** the id used in this table is ((iId<<8) + iMeta). */ "CREATE TABLE %Q.'%q_meta'(" "id INTEGER PRIMARY KEY, " "val BLOB" ");" ; int ii; char *zMeta = 0; for(ii=0; iinMeta meta-list builders */ }; #define VEC1_BLOCKED_NONE 0 #define VEC1_BLOCKED_PQ 1 #define VEC1_BLOCKED_BIN 2 /* ** An object of this type is used to accumulate one or more new vector ** index entries in memory before they are flushed to disk. */ typedef struct Vec1Writer Vec1Writer; struct Vec1Writer { Vec1Tab *pTab; /* Vec1 table object */ int bRebuild; /* True if this is a 'rebuild' op */ float *aResidual; /* Temp space for residual vector */ u8 *aPQ; /* Temp space for PQ */ int nBld; /* Size of aBld[] */ Vec1ListBuilder *aBld; /* Array of list builders */ }; #define VEC1_INSTRUMENT_QUERIES 1 #ifdef VEC1_INSTRUMENT_QUERIES # define VEC1_QINSTR_TOTAL 0 # define VEC1_QINSTR_INITQUERY 1 # define VEC1_QINSTR_COARSE 2 # define VEC1_QINSTR_IDXREAD 3 # define VEC1_QINSTR_METAREAD 4 # define VEC1_QINSTR_METASCAN 5 # define VEC1_QINSTR_LUT 6 # define VEC1_QINSTR_PQSCAN 7 # define VEC1_QINSTR_FINALSORT 8 # define VEC1_QINSTR_NCOUNTER 9 # define VEC1_QINSTR_START(pTab, eCounter) { \ (pTab)->aCycle[eCounter] -= vec1HardwareTimer(); \ } # define VEC1_QINSTR_STOP(pTab, eCounter) { \ (pTab)->aCount[eCounter]++; \ (pTab)->aCycle[eCounter] += vec1HardwareTimer(); \ } #else # define VEC1_QINSTR_STOP(pTab, eCounter) # define VEC1_QINSTR_START(pTab, eCounter) #endif #define VEC1_META_COLUMN_BITS 8 #define VEC1_MAX_META_COLUMNS (1<base.zErrMsg); pTab->base.zErrMsg = zMsg; } /* ** Prepare the SQL statement passed as the second argument. If successful, ** set (*pStmt) to point to the prepared statement handle and return ** SQLITE_OK. It is the responsibility of the caller to eventually finalize ** the statement using sqlite3_finalize(). ** ** Or, if an error occurs, set (*ppStmt) to NULL, return an SQLite error ** code and leave an error message in the virtual table object. */ static int vec1PrepareSql( Vec1Tab *pTab, sqlite3_stmt **ppStmt, const char *zFmt, ... ){ char *zSql = 0; int rc = SQLITE_OK; va_list ap; va_start(ap, zFmt); zSql = sqlite3_vmprintf(zFmt, ap); va_end(ap); *ppStmt = 0; if( zSql ){ rc = sqlite3_prepare_v2(pTab->db, zSql, -1, ppStmt, 0); sqlite3_free(zSql); if( rc!=SQLITE_OK ){ vec1VtabError(pTab, "%s", sqlite3_errmsg(pTab->db)); } }else{ rc = SQLITE_NOMEM; } return rc; } #define VEC1_SQL_SCAN_BASE 1 #define VEC1_SQL_INSERT_IDX 2 #define VEC1_SQL_SCAN_IDX 3 #define VEC1_SQL_CNT_IDX 4 #define VEC1_SQL_FILTER_IDX 5 #define VEC1_SQL_WRITE_CONFIG 5 #define VEC1_SQL_INCR_CONFIG 6 #define VEC1_SQL_LOAD_CONFIG 7 static int vec1GetSql(Vec1Tab *pTab, int eSql, sqlite3_stmt **ppStmt){ const char *azSql[] = { #define VEC1_SQL_INSERT_BASE 0 "INSERT INTO %Q.'%q_base' VALUES(?, ?%s)", "SELECT * FROM %Q.'%q_base' ORDER BY 1", "INSERT INTO %Q.'%q_idx'(id, bucket, first, last, val)VALUES(?,?,?,?,?)", "SELECT val, bucket, rowid FROM %Q.'%q_idx'", "SELECT sum( length(val) / ? ) FROM %Q.'%q_idx'", "REPLACE INTO %Q.'%q_config'(id, val) VALUES(?, ?)", "INSERT INTO %Q.'%q_config'(id, val) VALUES(?, 1) " "ON CONFLICT(id) DO UPDATE SET val=val+1", "SELECT id, val FROM %Q.'%q_config'", #define VEC1_SQL_LOOKUP_BASE 8 "SELECT * FROM %Q.'%q_base' WHERE id=?", #define VEC1_SQL_EDIT_IDX 9 "SELECT id, min( length(val) ) FROM %Q.'%q_idx' WHERE bucket=?", #define VEC1_SQL_DEL_LOOKUP_BASE 10 /* Used when removing a row by id. This statement deletes the %_base ** entry and returns the vector so that the user may use it to ** find the %_idx entry to delete. */ "DELETE FROM %Q.'%q_base' WHERE id=? RETURNING vector", #define VEC1_SQL_SEARCH_IDX 11 "SELECT id FROM %Q.'%q_idx' WHERE bucket=? AND ? BETWEEN first AND last", #define VEC1_SQL_SEARCH_IDX_FOR_INSERT 12 "SELECT id, first, last, val " "FROM %Q.'%q_idx' WHERE bucket=? AND length(val)=0 && eSqlaStmt) ); if( pTab->aStmt[eSql]==0 ){ if( eSql==VEC1_SQL_INSERT_BASE ){ int ii; char *zExtra = 0; for(ii=0; iinMeta; ii++){ zExtra = vec1MPrintf(&rc, "%z,?", zExtra); } if( rc==SQLITE_OK ){ rc = vec1PrepareSql( pTab, &pTab->aStmt[eSql], azSql[eSql], pTab->zDb, pTab->zName, zExtra ); } sqlite3_free(zExtra); }else{ rc = vec1PrepareSql( pTab, &pTab->aStmt[eSql], azSql[eSql], pTab->zDb, pTab->zName ); } } *ppStmt = pTab->aStmt[eSql]; return rc; } static void vec1UnloadModel(Vec1Tab *pTab){ sqlite3_free(pTab->pModBlob); sqlite3_free(pTab->mod.aModelT); vec1ModelFreeTCent(&pTab->mod); pTab->pModBlob = 0; pTab->cfg.iModelVersion = 0; memset(&pTab->mod, 0, sizeof(pTab->mod)); } static void vec1UnloadConfig(Vec1Tab *pTab){ pTab->cfg.nElem = 0; } /* ** xDisconnect method. Free the virtual table object passed as the only ** argument. */ static int vec1DisconnectMethod(sqlite3_vtab *pVtab){ Vec1Tab *pTab = (Vec1Tab*)pVtab; int ii; /* Remove this table object from the database handle list of vec1 tables */ if( pTab->pTabList ){ Vec1Tab **pp = &pTab->pTabList->pFirst; while( *pp!=pTab ) pp = &(*pp)->pTabNext; *pp = pTab->pTabNext; } for(ii=0; iiaStmt); ii++){ sqlite3_finalize(pTab->aStmt[ii]); } vec1UnloadModel(pTab); sqlite3_free(pTab->zDb); sqlite3_free(pTab->zName); sqlite3_free(pTab->zTrainTbl); sqlite3_free(pTab->zIdxTbl); sqlite3_free(pTab->aTmpVec); sqlite3_free(pTab); return SQLITE_OK; } static int vec1SqlExec(Vec1Tab *pTab, const char *zSqlfmt, char **pzErr){ char *zSql = 0; int rc = SQLITE_OK; zSql = sqlite3_mprintf(zSqlfmt, pTab->zDb, pTab->zName); if( zSql==0 ){ rc = SQLITE_NOMEM; }else{ rc = sqlite3_exec(pTab->db, zSql, 0, 0, pzErr); sqlite3_free(zSql); } return rc; } static void vec1StmtReset(int *pRc, sqlite3_stmt *pStmt){ int rc = sqlite3_reset(pStmt); if( *pRc==SQLITE_OK ) *pRc = rc; } static void vec1StmtFinalize(int *pRc, sqlite3_stmt *pStmt){ int rc = sqlite3_finalize(pStmt); if( *pRc==SQLITE_OK ) *pRc = rc; } /* ** Return true if character c is an ASCII range whitespace character. */ static int vec1IsWhitespace(char c){ return (c==' ' || c=='\t' || c=='\r' || c=='\n'); } /* ** Check that zName is a legal name for a vec1 column. A legal column name ** is any single token or quoted string, with no trailing garbage. For ** the purposes of this function, a token is a contiguous sequence of ** non-whitespace characters that do not begin with a quote character. ** ** If zName is a legal name (or an illegal name that will be detected by ** sqlite3_declare_vtab()), SQLITE_OK is returned and (*pzErr) is left ** unchanged. Or, if it is not a legal name, SQLITE_ERROR is returned, and ** *pzErr is left to point to a buffer containing an English language ** error message. It is the responsibility of the caller to eventually ** free this buffer using sqlite3_free(). */ static int vec1CheckColumnName(const char *zName, char **pzErr){ char aName[16]; int nName = 0; const char *z = zName; while( vec1IsWhitespace(*z) ) z++; if( (*z)=='"' || (*z)=='\'' || (*z)=='`' ){ char q = *z++; while( 1 ){ if( (*z)=='\0' ){ *pzErr = sqlite3_mprintf("vec1: unmatched quote character"); return SQLITE_ERROR; } if( (*z)==q ){ z++; if( (*z)!=q ) break; } if( nName<(int)sizeof(aName) ) aName[nName++] = *z; z++; } } else if( (*z)=='[' ){ z++; while( (*z) && (*z)!=']' ){ if( nName<(int)sizeof(aName) ) aName[nName++] = *z; z++; } if( (*z)=='\0' ){ *pzErr = sqlite3_mprintf("vec1: unmatched [ character"); return SQLITE_ERROR; } z++; } else { while( (*z) && 0==vec1IsWhitespace(*z) ){ if( nName<(int)sizeof(aName) ) aName[nName++] = *z; z++; } } while( vec1IsWhitespace(*z) ) z++; if( (*z)!='\0' ){ *pzErr = sqlite3_mprintf("vec1: trailing characters after column name"); return SQLITE_ERROR; } if( nName==5 && 0==sqlite3_strnicmp(aName, "rowid", 5) || nName==8 && 0==sqlite3_strnicmp(aName, "distance", 8) ){ *pzErr = sqlite3_mprintf("vec1: reserved column name: %s", zName); return SQLITE_ERROR; } return SQLITE_OK; } /* ** Invoke sqlite3_declare_vtab() for this table. */ static int vec1DeclareVtab( sqlite3 *db, int argc, const char *const* argv, char **pzErr ){ const char *zVector = "vector"; char *zExtra = 0; char *zCreate = 0; int rc = SQLITE_OK; int ii; for(ii=3; rc==SQLITE_OK && ii3 ){ zVector = argv[3]; } for(ii=4; iizDb = vec1Strdup(&rc, argv[1]); pTab->zName = vec1Strdup(&rc, argv[2]); pTab->zTrainTbl = vec1MPrintf(&rc, "%s_model", argv[2]); pTab->zIdxTbl = vec1MPrintf(&rc, "%s_idx", argv[2]); pTab->db = db; pTab->mod.hdr.eDistance = VEC1_DISTANCE_L2; pTab->nMeta = MAX(argc-4, 0); if( pTab->nMeta>VEC1_MAX_META_COLUMNS ){ *pzErr = sqlite3_mprintf( "too many meta-data columns (max is %d)", VEC1_MAX_META_COLUMNS ); rc = SQLITE_ERROR; } if( rc==SQLITE_OK ){ rc = vec1DeclareVtab(db, argc, argv, pzErr); } if( rc==SQLITE_OK && bCreate ){ char *zShadowSchema = vec1ShadowSchema(pTab->zDb, pTab->zName, pTab->nMeta); if( zShadowSchema==0 ){ rc = SQLITE_NOMEM; }else{ rc = sqlite3_exec(db, zShadowSchema, 0, 0, pzErr); sqlite3_free(zShadowSchema); } if( rc==SQLITE_OK ){ sqlite3_stmt *pStmt = 0; rc = vec1GetSql(pTab, VEC1_SQL_WRITE_CONFIG, &pStmt); if( rc==SQLITE_OK ){ sqlite3_bind_int(pStmt, 1, VEC1_CONFIG_FMTVERSION); sqlite3_bind_int(pStmt, 2, VEC1_CURRENT_FMTVERSION); sqlite3_step(pStmt); vec1StmtReset(&rc, pStmt); } } } if( rc!=SQLITE_OK ){ if( pTab->base.zErrMsg ){ *pzErr = pTab->base.zErrMsg; pTab->base.zErrMsg = 0; } vec1DisconnectMethod((sqlite3_vtab*)pTab); return rc; } /* Add the new table to the database handle list of vec1 tables. It ** will be eventually removed by vec1DisconnectMethod(). */ pTab->pTabList = pTabList; pTab->pTabNext = pTabList->pFirst; pTabList->pFirst = pTab; *ppVtab = &pTab->base; return SQLITE_OK; } /* ** xCreate / xConnect */ static int vec1ConnectMethod( sqlite3 *db, void *p, /* Pointer to Vec1TabList for this db */ int argc, const char *const *argv, sqlite3_vtab **ppVtab, char **pzErr ){ return vec1CreateConnect(0, (Vec1TabList*)p, db, argc, argv, ppVtab, pzErr); } static int vec1CreateMethod( sqlite3 *db, void *p, /* Pointer to Vec1TabList for this db */ int argc, const char *const *argv, sqlite3_vtab **ppVtab, char **pzErr ){ return vec1CreateConnect(1, (Vec1TabList*)p, db, argc, argv, ppVtab, pzErr); } static int vec1DestroyMethod(sqlite3_vtab *pVtab){ Vec1Tab *pTab = (Vec1Tab*)pVtab; int rc = SQLITE_OK; char *zDrop = vec1DropShadowSchema(pTab->zDb, pTab->zName); if( zDrop==0 ){ rc = SQLITE_NOMEM; }else{ rc = sqlite3_exec(pTab->db, zDrop, 0, 0, 0); sqlite3_free(zDrop); } if( rc==SQLITE_OK ){ vec1DisconnectMethod(pVtab); } return rc; } /* ** Cursor methods */ static int vec1Open( sqlite3_vtab *pVtab, sqlite3_vtab_cursor **ppCursor ){ Vec1Csr *pCur; UNUSED_PARAMETER(pVtab); pCur = sqlite3_malloc(sizeof(*pCur)); if( pCur==0 ){ return SQLITE_NOMEM; } memset(pCur, 0, sizeof(*pCur)); *ppCursor = &pCur->base; return SQLITE_OK; } static void vec1QueryFree(Vec1Query *pQuery){ if( pQuery ){ int ii; for(ii=0; iinFilter; ii++){ sqlite3_free(pQuery->aFilter[ii].pPtr); } sqlite3_free(pQuery->heap.aRes); sqlite3_free(pQuery->aBucket); sqlite3_free(pQuery); } } static void vec1ResetCsr(Vec1Csr *pCsr){ vec1QueryFree(pCsr->pQuery); sqlite3_finalize(pCsr->pStmt); pCsr->pStmt = 0; pCsr->bSeek = 0; sqlite3_free(pCsr->zDistance); pCsr->zDistance = 0; pCsr->bSqlNNQuery = 0; } static int vec1Close(sqlite3_vtab_cursor *cur){ Vec1Csr *pCsr = (Vec1Csr*)cur; vec1ResetCsr(pCsr); sqlite3_free(pCsr); return SQLITE_OK; } static void vec1FlatIterNext(Vec1FlatIter *pIter){ while( 1 ){ pIter->ii++; if( pIter->ii>=pIter->nEntry ){ pIter->iRowid = 0; pIter->aVec = 0; break; }else{ i64 iRowid = 0; int iOff = VEC1_LIST_SZHDR + pIter->ii * pIter->szRowid; if( pIter->szRowid==4 ){ iRowid = (i64)vec1GetU32(&pIter->aBlob[iOff]); if( iRowid==VEC1_TOMBSTONE_32 ) continue; }else{ iRowid = (i64)vec1GetU64(&pIter->aBlob[iOff]); if( iRowid==VEC1_TOMBSTONE_64 ) continue; } iOff = VEC1_LIST_SZHDR + (pIter->nEntry * pIter->szRowid) + (pIter->ii * pIter->szVec); pIter->aVec = &pIter->aBlob[iOff]; pIter->iRowid = iRowid; break; } } } /* ** Return the encoded vector size for the model. Two cases: ** ** a) PQ encoding. In this case encoded vector size is hdr.nCodebook. ** ** b) Bitwise encoding. In this case encoded vector size is 4 bytes + ** 1 bit per dimension. */ static int vec1EncodedVectorSize(const Vec1Model *pMod){ int ret = (int)pMod->hdr.nCodebook; if( pMod->hdr.flags & VEC1_MODEL_BITQUANT ) ret += sizeof_f32; return ret; } /* ** nBlob byte buffer aBlob[] contains a list read from the %_idx table. ** This function returns zero if the list size matches the embedded ** number-of-entries field, and so the list may be accessed safely without ** bounds checking, or non-zero otherwise. */ static int vec1CheckIdxSize(Vec1Tab *p, const u8 *aBlob, i64 nBlob){ int bRet = 1; /* If the blob is smaller than 8 bytes, it must be corrupt */ if( nBlob>=VEC1_LIST_SZHDR ){ /* Read the flags and number-of-entries fields */ u32 flags = vec1GetU32(&aBlob[0]); i64 nEntry = (int)vec1GetU32(&aBlob[4]); const i64 szRowid = ((flags & VEC1_LIST_64BIT) ? 8 : 4); if( p->mod.hdr.nCodebook>0 ){ const i64 nBlk = ((nEntry+VEC1_PQ_BLOCKSIZE-1) / VEC1_PQ_BLOCKSIZE); const i64 szBlk = VEC1_PQ_BLOCKSIZE * vec1EncodedVectorSize(&p->mod); bRet = (nBlob!=(VEC1_LIST_SZHDR + nEntry*szRowid + nBlk*szBlk)); }else{ const i64 szVec = p->cfg.nElem*sizeof_f32; bRet = (nBlob!=(VEC1_LIST_SZHDR + nEntry*szRowid + nEntry*szVec)); } } return bRet; } static int vec1FlatIterStart( Vec1Tab *pTab, Vec1FlatIter *pIter, const u8 *aBlob, int nBlob ){ u32 flags; pIter->aBlob = aBlob; pIter->nBlob = nBlob; pIter->ii = 0; if( vec1CheckIdxSize(pTab, aBlob, nBlob) ){ return VEC1_CORRUPT; } flags = vec1GetU32(aBlob); pIter->szRowid = (flags & VEC1_LIST_64BIT) ? 8 : 4; pIter->nEntry = vec1GetU32(&aBlob[4]); pIter->szVec = (pTab->cfg.nElem * sizeof_f32); pIter->ii = -1; vec1FlatIterNext(pIter); return SQLITE_OK; } /* ** Merge the two sorted subarrays a[p..q-1] and a[q..r-1] using ** the temporary buffer aTmp. */ static void vec1Merge( Vec1AnnResult *a, Vec1AnnResult *aTmp, int p, int q, int r ){ int i = p; int j = q; int k = p; while( i1 ){ i64 nByte = sizeof(Vec1AnnResult) * n; Vec1AnnResult *aTmp = (Vec1AnnResult*)sqlite3_malloc64(nByte); if( aTmp==0 ){ rc = SQLITE_NOMEM; }else{ vec1MergeSort(a, aTmp, 0, (int)n); sqlite3_free(aTmp); } } return rc; } static int vec1DoKANNBucket(Vec1Query*,int,Vec1Buffer*,Vec1Buffer*,Vec1Buffer*); static int vec1StreamingNext(Vec1Csr *pCsr){ int rc = SQLITE_OK; Vec1Query *pQuery = pCsr->pQuery; Vec1AnnHeap *pHeap = &pQuery->heap; int ii; int nSort = (int)(pHeap->nRes - pCsr->nCurrentRes); { i64 nRes = pHeap->nRes; pHeap->nRes = 0; for(ii=pCsr->nCurrentRes; iiaRes[ii].iRowid, pHeap->aRes[ii].fDist); } } while( pQuery->nBucket>0 && nSort<=(pQuery->nOrigRes/2) ){ Vec1Buffer a = {0,0,0}; Vec1Buffer b = {0,0,0}; Vec1Buffer c = {0,0,0}; int iBest = 0; int iBucket = 0; /* Find the nearest remaining bucket to the query vector. Remove it ** from the aBucket[] array. */ for(ii=1; iinBucket; ii++){ if( pQuery->aBucket[ii].fDistaBucket[iBest].fDist ){ iBest = ii; } } iBucket = pQuery->aBucket[iBest].iBucket; pQuery->nBucket--; if( iBest!=pQuery->nBucket ){ pQuery->aBucket[iBest] = pQuery->aBucket[pQuery->nBucket]; } rc = vec1DoKANNBucket(pQuery, iBucket, &a, &b, &c); vec1BufferFree(&a); vec1BufferFree(&b); vec1BufferFree(&c); if( rc!=SQLITE_OK ) return rc; } nSort = (int)MIN(pHeap->nMax, pHeap->nRes); rc = vec1AnnResultSort(pHeap->aRes, nSort); if( rc==SQLITE_OK ){ pCsr->iCurrentRes = 0; pCsr->nCurrentRes = nSort; } return rc; } /* ** xNext() method for vec1 cursors. */ static int vec1NextMethod(sqlite3_vtab_cursor *cur){ int rc = SQLITE_OK; Vec1Csr *pCsr = (Vec1Csr *)cur; if( pCsr->zDistance ){ sqlite3_free(pCsr->zDistance ); pCsr->zDistance = 0; } if( pCsr->pQuery ){ pCsr->iCurrentRes++; pCsr->bSeek = 0; if( pCsr->iCurrentRes>=pCsr->nCurrentRes ){ /* If this is a streaming query, try to get some more results. */ if( pCsr->pQuery->bStreaming ){ rc = vec1StreamingNext(pCsr); } /* If this was not a streaming query, or no further results are ** available, set pStmt to 0 to indicate EOF. */ if( pCsr->iCurrentRes>=pCsr->nCurrentRes ){ sqlite3_finalize(pCsr->pStmt); pCsr->pStmt = 0; } } sqlite3_reset(pCsr->pStmt); }else{ rc = sqlite3_step(pCsr->pStmt); if( rc!=SQLITE_ROW ){ rc = sqlite3_finalize(pCsr->pStmt); pCsr->pStmt = 0; }else{ rc = SQLITE_OK; } } return rc; } static int vec1ModelTransform( Vec1Model *pMod, float **paOut ){ if( pMod->aModel ){ const int nCodebook = pMod->hdr.nCodebook; const int nCodeElem = pMod->nCodeElem; int M; float *pOut = (float*)sqlite3_malloc( sizeof_f32 * nCodebook * nCodeElem * VEC1_PQ_CODEBOOK_SZ ); if( pOut==0 ) return SQLITE_NOMEM; *paOut = pOut; /* For VEC1_TRANSPOSE_WIDTH==8, the order of elements in output is: ** ** aModel[M=0][d=0] [K=0..7] ** aModel[M=0][d=1] [K=0..7] ** aModel[M=0][d=2] [K=0..7] ** ... ** aModel[M=0][d=(nCodeElem-1)][K=0..7] ** aModel[M=0][d=0] [K=8..15] ** aModel[M=0][d=1] [K=8..15] ** aModel[M=0][d=2] [K=8..15] ** ... ** aModel[M=0][d=(nCodeElem-1)][K=248..255] ** aModel[M=1][d=0] [K=0..7] ** ... */ for(M=0; MaModel[ M * nCodeElem * VEC1_PQ_CODEBOOK_SZ ], nCodeElem, VEC1_TRANSPOSE_WIDTH, VEC1_PQ_CODEBOOK_SZ, &pOut[ M * nCodeElem * VEC1_PQ_CODEBOOK_SZ ] ); } } return SQLITE_OK; } /* ** If it is not already loaded, load the PQ model from disk. */ static int vec1LoadModel(Vec1Tab *pTab){ int rc = SQLITE_OK; sqlite3 *db = pTab->db; sqlite3_blob *pBlob = 0; vec1UnloadModel(pTab); rc = sqlite3_blob_open(db, pTab->zDb, pTab->zTrainTbl, "val", 1, 0, &pBlob); if( rc==SQLITE_OK ){ int nByte = sqlite3_blob_bytes(pBlob); void *pData = sqlite3_malloc(nByte); if( pData==0 ){ rc = SQLITE_NOMEM; }else{ rc = sqlite3_blob_read(pBlob, pData, nByte, 0); } if( rc==SQLITE_OK ){ rc = vec1DecodeModel(pData, nByte, &pTab->mod, &pTab->base.zErrMsg); } if( rc==SQLITE_OK ){ pTab->pModBlob = pData; }else{ sqlite3_free(pData); } sqlite3_blob_close(pBlob); } if( rc==SQLITE_OK ){ rc = vec1ModelTransform(&pTab->mod, &pTab->mod.aModelT); } return rc; } #if 0 static int vec1AnnResultPartition(Vec1AnnResult *aArr, int iLeft, int iRight){ double fPivot = aArr[iRight].fDist; /* choose last element */ int iSmall = iLeft; /* Next small element goes here */ int ii; for(ii=iLeft; iiK ){ iRight = iPivot - 1; } else { iLeft = iPivot + 1; } } } #endif /* ** Merge the two sorted subarrays a[p..q-1] and a[q..r-1] using ** the temporary buffer aTmp. */ static void vec1BucketMerge( Vec1BucketResult *a, Vec1BucketResult *aTmp, int p, int q, int r ){ int i = p; int j = q; int k = p; while( i1 ){ i64 nByte = sizeof(Vec1BucketResult) * n; Vec1BucketResult *aTmp = (Vec1BucketResult*)sqlite3_malloc64(nByte); if( aTmp==0 ){ rc = SQLITE_NOMEM; }else{ vec1BucketMergeSort(a, aTmp, 0, (int)n); sqlite3_free(aTmp); } } return rc; } /* ** Partition the part of the array between elements iLeft and iRight ** (inclusive). Return the index of the partition key. */ static int vec1BucketPartition(Vec1BucketResult *aArr, int iLeft, int iRight){ float fPivot = aArr[iRight].fDist; /* choose last element */ int iSmall = iLeft; /* Next small element goes here */ int ii; for(ii=iLeft; ii=0 ); while( 1 ){ int iPivot = vec1BucketPartition(aArr, iLeft, iRight); if( iPivot==K ){ return; }else if( iPivot>K ){ iRight = iPivot - 1; } else { iLeft = iPivot + 1; } } } static int vec1FindBuckets( Vec1Tab *pTab, const float *aVec, int nProbe, Vec1BucketResult **paBucket ){ int nBucket = pTab->mod.hdr.nBucket; int nElem = pTab->mod.hdr.nElem; Vec1BucketResult *aRes = 0; int rc = SQLITE_OK; aRes = (Vec1BucketResult*)sqlite3_malloc(sizeof(Vec1BucketResult) * nBucket); if( aRes ){ int ii; for(ii=0; iimod.aCentroid[ii*nElem]; aRes[ii].iBucket = ii; aRes[ii].fDist = (float)vec1L2Dist(aVec, aCentroid, nElem); } if( nProbe=aRes[nProbe].fDist ); } } #endif #if 0 for(ii=0; iihdr.flags & VEC1_MODEL_ROTATE) ){ vec1RotateVector(pMod->hdr.nElem, pMod->aRotation, aInput, aTmp); if( pMod->hdr.eDistance==VEC1_DISTANCE_COS ){ vec1NormalizeVector(aTmp, pMod->hdr.nElem); } aRet = aTmp; } else if( (pMod->hdr.flags & VEC1_MODEL_WHT) ){ memcpy(aTmp, aInput, pMod->hdr.nElem * sizeof_f32); vec1ApplyWHT(aTmp, pMod->hdr.nElem); aRet = aTmp; } else if( pMod->hdr.eDistance==VEC1_DISTANCE_COS ){ memcpy(aTmp, aInput, pMod->hdr.nElem * sizeof_f32); vec1NormalizeVector(aTmp, pMod->hdr.nElem); aRet = aTmp; } if( aRet!=aTmp && pMod->hdr.nCodebook>0 && (pMod->hdr.nCodebook*pMod->nCodeElem)!=pMod->hdr.nElem ){ memcpy(aTmp, aRet, pMod->hdr.nElem * sizeof_f32); aRet = aTmp; } return aRet; } /* ** Return true if vec1TransformRequired() is not a no-op. i.e. if any ** transformation is required for input vectors. */ static int vec1TransformRequired(const Vec1Model *pMod){ Vec1ModelHeader const *p = &pMod->hdr; return (p->flags & (VEC1_MODEL_ROTATE|VEC1_MODEL_WHT)) || (p->eDistance==VEC1_DISTANCE_COS); } /* ** This routine is the core of both the LUT builder and the vector encoder. ** ** pIn: ** pIn is an array of VEC1_TRANSPOSE_WIDTH nCodeElem-dimension vectors in ** column major format. That is to say, pIn[0] is the first element of ** the first vector, pIn[1] is the first element of the second vector, ** and so on. ** ** In other words, to find element i of vector v: ** ** pIn[i * VEC1_TRANSPOSE_WIDTH + v] ** ** aVec: ** A single vector with nCodeElem elements.. ** ** nCodeElem: ** Size of vectors in elements. ** ** aOut: ** Array of VEC1_TRANSPOSE_WIDTH. Populated with the square of the L2 distance ** between aVec[] and each of the vectors in pIn. */ static void vec1ModelTDist( const float *pIn, const float *aVec, int nCodeElem, float *aOut ){ int d; #if defined(VEC1_HAVE_AVX2) __m256 acc[ VEC1_TRANSPOSE_WIDTH / 8 ]; const int nAcc = (VEC1_TRANSPOSE_WIDTH / 8); int c; for(c=0; c>7)&0x7f)|0x80; aBuf[1] = v&0x7f; return 2; } { u8 a[10]; int ii; int nRet; for(nRet=0; v; nRet++){ a[nRet] = (v&0x7F); v = v >> 7; } for(ii=0; iimod.hdr.nCodebook; const int nCodeElem = pTab->mod.nCodeElem; int M, K; float *pIn = pTab->mod.aModelT; for(M=0; Mcfg.nElem; int nCodebook = pTab->mod.hdr.nCodebook; const int nCodeElem = pTab->mod.nCodeElem; int M; float c = 1.0f / sqrtf((float)nVec); assert( nCodeElem==8 || nCodeElem==16 ); for(M=0; M=0 && iMetan = 0; sqlite3_bind_int64(pStmt, 1, iMetaId); if( SQLITE_ROW==sqlite3_step(pStmt) ){ int nByte = sqlite3_column_bytes(pStmt, 0); rc = vec1BufferGrow(pBuf, nByte + VEC1_META_PADDING); if( rc==SQLITE_OK ){ const u8 *a = sqlite3_column_blob(pStmt, 0); memcpy(pBuf->a, a, nByte); memset(&pBuf->a[nByte], 0, VEC1_META_PADDING); pBuf->n = nByte; } } vec1StmtReset(&rc, pStmt); /* Check, so far as is possible, that the meta-value blob is the ** expected size. It is not possible to check if the meta-value blob ** uses the GENERIC format, due to the variable length fields. Code ** that handles GENERIC meta-value blobs has to dynamically verify ** the buffer size. */ if( rc==SQLITE_OK ){ if( pBuf->na[0]) & VEC1_META_TYPEMASK); u32 nEntry = vec1GetU32(&pBuf->a[4]); i64 sz = 0; if( nEntry!=nExpect ){ rc = VEC1_CORRUPT; } if( tflag==VEC1_META_1BYTEINT ){ sz = 1; }else if( tflag==VEC1_META_4BYTEINT ){ sz = sizeof_u32; }else if( tflag==VEC1_META_REAL ){ sz = sizeof_f64; }else if( tflag!=VEC1_META_GENERIC ){ rc = VEC1_CORRUPT; } if( sz && pBuf->n!=(VEC1_META_SZHDR + sz*nEntry) ){ rc = VEC1_CORRUPT; } } } } return rc; } /* ** 0: NULL value. 0 byte payload. ** 1: Integer value. 1 byte payload. ** 2: Integer value. 4 byte payload. ** 3: Integer value. 8 byte payload. ** 4: Real value. 8 byte payload. */ static int vec1MetaValueRead( Vec1Buffer *pMeta, int *piOff, Vec1MetaValue *pVal ){ i64 iOff = (i64)*piOff; switch( pMeta->a[iOff] ){ case 0: { pVal->eType = SQLITE_NULL; iOff++; break; } case 1: { pVal->eType = SQLITE_INTEGER; pVal->iVal = (i64)(u8)pMeta->a[iOff+1]; iOff += 2; break; } case 2: { pVal->eType = SQLITE_INTEGER; pVal->iVal = (int)vec1GetU32(&pMeta->a[iOff+1]); iOff += 5; break; } case 3: { pVal->eType = SQLITE_INTEGER; pVal->iVal = (i64)vec1GetU64(&pMeta->a[iOff+1]); iOff += 9; break; } case 4: { u64 iVal = vec1GetU64(&pMeta->a[iOff+1]); memcpy(&pVal->fVal, &iVal, sizeof(u64)); pVal->eType = SQLITE_FLOAT; iOff += 9; break; } default: { u64 eType = 0; iOff += vec1GetVarint(&pMeta->a[iOff], &eType); pVal->pPtr = &pMeta->a[iOff]; pVal->iVal = (((eType-5) / 2) & (u64)0xFFFFFFFF); iOff += pVal->iVal; pVal->eType = (eType & 0x01) ? SQLITE_TEXT : SQLITE_BLOB; break; } } if( iOff>pMeta->n || iOff<0 ){ return VEC1_CORRUPT; } *piOff = (int)iOff; return SQLITE_OK; } /* ** Compare numeric values a and b. Return -1, 0 or +1 if a is smaller than, ** equal to, or greater than b, respectively. i.e. (a) - (b). */ #define COMPARE(a, b) ((a)==(b) ? 0 : ((a)>(b) ? +1 : -1)) /* ** Return 0 if the meta value passes the filter, or 1 if it is excluded ** by the filter. This is the slow path that can do any operator for ** any types of values. */ static int vec1MetaValueFilter(Vec1Filter *pFilter, Vec1MetaValue *pMeta){ int cmp = 0; if( pFilter->vop==VEC1_OP_IN ){ int ii; for(ii=0; iiiVal; ii++){ if( vec1MetaValueFilter(&pFilter[ii+1], pMeta)==0 ) return 0; } return 1; } if( pMeta->eType==SQLITE_INTEGER ){ switch( pFilter->eType ){ case SQLITE_NULL: return 1; case SQLITE_INTEGER: { cmp = COMPARE(pFilter->iVal, pMeta->iVal); break; } case SQLITE_FLOAT: { cmp = COMPARE(pFilter->fVal, (double)pMeta->iVal); break; } default: { /* All text and blob are bigger than all numbers */ cmp = +1; break; } } } else if( pMeta->eType==SQLITE_FLOAT ){ switch( pFilter->eType ){ case SQLITE_NULL: return 1; case SQLITE_INTEGER: { cmp = COMPARE((double)pFilter->iVal, pMeta->fVal); break; } case SQLITE_FLOAT: { cmp = COMPARE(pFilter->fVal, pMeta->fVal); break; } default: { /* All text and blob are bigger than all numbers */ cmp = +1; break; } } }else if( pMeta->eType==SQLITE_NULL ){ if( pFilter->vop==VEC1_OP_IS && pFilter->eType==SQLITE_NULL ) return 0; return 1; }else if( pMeta->eType==SQLITE_TEXT ){ switch( pFilter->eType ){ case SQLITE_NULL: return 1; case SQLITE_INTEGER: case SQLITE_FLOAT: { cmp = -1; break; } case SQLITE_TEXT: { i64 n = MIN(pMeta->iVal, pFilter->iVal); if( n>0 ){ cmp = memcmp(pFilter->pPtr, pMeta->pPtr, n); } if( cmp==0 ) cmp = COMPARE(pFilter->iVal, pMeta->iVal); break; } default: { assert( pFilter->eType==SQLITE_BLOB ); cmp = 1; break; } } }else{ assert( pMeta->eType==SQLITE_BLOB ); switch( pFilter->eType ){ case SQLITE_NULL: return 1; case SQLITE_INTEGER: case SQLITE_FLOAT: case SQLITE_TEXT: { cmp = -1; break; } default: { i64 n = MIN(pMeta->iVal, pFilter->iVal); if( n>0 ) cmp = memcmp(pFilter->pPtr, pMeta->pPtr, n); if( cmp==0 ) cmp = COMPARE(pFilter->iVal, pMeta->iVal); assert( pFilter->eType==SQLITE_BLOB ); break; } } } switch( pFilter->vop ){ case VEC1_OP_IS: case VEC1_OP_EQ: return !(cmp==0); case VEC1_OP_LT: return !(cmp>0); case VEC1_OP_LE: return !(cmp>=0); case VEC1_OP_GT: return !(cmp<0); case VEC1_OP_GE: return !(cmp<=0); } assert( pFilter->vop==VEC1_OP_NOTNULL ); return 0; } /* ** This is called as part of meta-data filtering when the meta-data field ** in question is stored in either VEC1_META_1BYTEINT or VEC1_META_4BYTEINT ** format. ** ** iMin, iMax, eNull: ** These three parameters describe the integer format used to store ** the meta-data field (either VEC1_META_1BYTEINT or VEC1_META_4BYTEINT). ** They pass the minimum integer value, maximum possible integer value ** and the value used to indicate NULL. So, for the two formats, they ** are: ** ** VEC1_META_1BYTEINT: 0, 254, 255. ** VEC1_META_4BYTEINT: -2147483648, +2147483646, +2147483647 ** ** pOp: ** Output parameter. If it is guaranteed that no entries in the meta-data ** field will match the filter (i.e. so zero rows will be returned), set ** this to 0. Otherwise, set it to one of the VEC1_OP_XXX constants to ** indicate the operation that should be used to test each value in the ** meta-data field. ** ** pRval: ** OUTPUT parameter. If (*pOp) is VEC1_OP_IN, then this is set to the ** number of entries in the (*paRval) array output. Otherwise, it is ** set to the integer value to use as the RHS of the comparison when ** testing each meta-data value. ** ** paRval: ** OUTPUT parameter. If (*pOp) is VEC1_OP_IN, an array of values to ** compare each meta-data value to. Otherwise, 0. ** ** Return SQLITE_OK if successful, or an SQLite error code (SQLITE_NOMEM) ** if an error occurs. */ static int vec1FilterToIntOp( Vec1Filter *pFilter, /* The constraint doing the filtering */ i64 iMin, i64 iMax, i64 eNull, /* Meta-data format description */ int *pRval, int **paRval, u8 *pOp ){ /* Set the following three values as follows: ** ** res: 0 if the result can be determined to be false for all list ** entries. 1 if it is true for all non-null list entries. Or ** -1 if scanning the list using op and rval is required to ** determine which list entries match the condition. ** ** op: If res is -1, one of VEC1_OP_EQ, VEC1_OP_GT, VEC1_OP_GE, ** VEC1_OP_LT or VEC1_OP_LE. ** ** rval: A value to compare using op with all list entries. ** ** Then, if res is not 0 or 1, run through the list comparing each ** list value with value rval using operation op. */ int res = -1; int rval = 0; u8 op = 0; assert( (iMin==0 && iMax==254 && eNull==255) || ( iMin==VEC1_META_4BYTEMIN && iMax==VEC1_META_4BYTEMAX && eNull==VEC1_META_4BYTENULL )); if( pFilter->vop==VEC1_OP_IN ){ int *aRval = (int*)vec1MallocZero(sizeof_u32 * (pFilter->iVal+1)); if( aRval==0 ){ return SQLITE_NOMEM; }else{ int ii; int nRval = 0; for(ii=0; iiiVal; ii++){ vec1FilterToIntOp(&pFilter[ii+1], iMin, iMax, eNull, &rval, 0, &op); if( op==VEC1_OP_EQ ){ aRval[nRval++] = rval; } } op = VEC1_OP_IN; rval = nRval; *paRval = aRval; } }else if( pFilter->vop==VEC1_OP_NOTNULL ){ res = 1; }else if( pFilter->eType==SQLITE_NULL && pFilter->vop==VEC1_OP_IS ){ rval = (int)eNull; op = VEC1_OP_EQ; }else if( pFilter->eType!=SQLITE_NULL ){ i64 iVal = pFilter->iVal; if( pFilter->eType==SQLITE_TEXT || pFilter->eType==SQLITE_BLOB ){ /* All text and blob values are greater than all 1-byte ints */ iVal = iMax+10; }else if( pFilter->eType==SQLITE_FLOAT ){ iVal = (i64)pFilter->fVal; if( (double)iVal!=pFilter->fVal ){ if( pFilter->vop==VEC1_OP_EQ || pFilter->vop==VEC1_OP_IS ){ iVal = iMin-10; }else if( pFilter->vop==VEC1_OP_LT || pFilter->vop==VEC1_OP_GE ){ iVal = iVal+1; } } } if( iValvop==VEC1_OP_GT || pFilter->vop==VEC1_OP_GE); }else if( iVal>iMax ){ res = (pFilter->vop==VEC1_OP_LT || pFilter->vop==VEC1_OP_LE); }else{ rval = (int)iVal; op = (pFilter->vop==VEC1_OP_IS ? VEC1_OP_EQ : pFilter->vop); } } if( res>0 ){ assert( eNull==(iMax+1) ); op = VEC1_OP_LT; rval = (int)eNull; } *pRval = rval; *pOp = op; return SQLITE_OK; } static void vec1MetaFilterIntTail( const u8 *aMeta, int iBitOff, u8 *aBitmask, int nEntry, int szInt, int rval, int *aRval, u8 op ){ const int eNull = szInt==1 ? VEC1_META_1BYTENULL : VEC1_META_4BYTENULL; int jj; assert( szInt==1 || szInt==4 ); for(jj=0; jjrval) && iVal!=eNull; break; case VEC1_OP_LT: bSet = (iVal=rval) && iVal!=eNull; break; case VEC1_OP_LE: bSet = (iVal<=rval); break; default: { int ii; assert( op==VEC1_OP_IN ); for(ii=0; ii0x01 */ hi = vshr_n_u8(hi, 7); m1 = (u16)vaddlvq_u16(vmull_u8(lo, w)); m2 = (u16)vaddlvq_u16(vmull_u8(hi, w)); } aBitmask[(jj/8)] |= ~m1; aBitmask[(jj/8)+1] |= ~m2; } #endif vec1MetaFilterIntTail( &a[jj], jj, aBitmask, nEntry-jj, 1, rval, aRval, op ); } static void vec1MetaFilterScan4ByteArray( const int *a, u8 *aBitmask, int nEntry, int rval, int *aRval, u8 op ){ int jj = 0; #if defined(VEC1_HAVE_AVX2) const __m256i rhs = _mm256_set1_epi32(rval); const __m256i null = _mm256_set1_epi32(VEC1_META_4BYTENULL); /* Shuffle mask used to swap the byte-order of the 32-bit integers ** in this meta-data list if required. There are no big-endian AVX2 ** systems, so no need for a runtime check here. */ const __m256i swap = _mm256_setr_epi8( 3,2,1,0, 7,6,5,4, 11,10,9,8, 15,14,13,12, 3,2,1,0, 7,6,5,4, 11,10,9,8, 15,14,13,12 ); for(; jj<(nEntry-7); jj+=8){ __m256i lhs = _mm256_loadu_si256((__m256i*)&a[jj]); u8 mask = 0; /* Shuffle the 8 32-bit int values to little-endian order */ lhs = _mm256_shuffle_epi8(lhs, swap); switch( op ){ case VEC1_OP_EQ: { __m256i cmp = _mm256_cmpeq_epi32(lhs, rhs); mask = (u8)_mm256_movemask_ps(_mm256_castsi256_ps(cmp)); break; } case VEC1_OP_GT: { __m256i cmp = _mm256_cmpgt_epi32(lhs, rhs); mask = (u8)_mm256_movemask_ps(_mm256_castsi256_ps(cmp)); cmp = _mm256_cmpeq_epi32(lhs, null); mask &= ~(u8)_mm256_movemask_ps(_mm256_castsi256_ps(cmp)); break; } case VEC1_OP_LT: { __m256i cmp = _mm256_cmpgt_epi32(rhs, lhs); mask = (u8)_mm256_movemask_ps(_mm256_castsi256_ps(cmp)); break; } case VEC1_OP_GE: { __m256i cmp = _mm256_cmpgt_epi32(rhs, lhs); mask = ~(u8)_mm256_movemask_ps(_mm256_castsi256_ps(cmp)); cmp = _mm256_cmpeq_epi32(lhs, null); mask &= ~(u8)_mm256_movemask_ps(_mm256_castsi256_ps(cmp)); break; } case VEC1_OP_LE: { __m256i cmp = _mm256_cmpgt_epi32(lhs, rhs); mask = ~(u8)_mm256_movemask_ps(_mm256_castsi256_ps(cmp)); break; } default: { int ii; assert( op==VEC1_OP_IN ); for(ii=0; iin<(VEC1_META_SZHDR+(nEntry*szInt)) ){ rc = VEC1_CORRUPT; }else{ int *aRval = 0; int rval = 0; u8 op = 0; rc = vec1FilterToIntOp(pFilter, (szInt==4 ? VEC1_META_4BYTEMIN : 0), (szInt==4 ? VEC1_META_4BYTEMAX : 254), (szInt==4 ? VEC1_META_4BYTENULL : VEC1_META_1BYTENULL), &rval, &aRval, &op ); if( rc==SQLITE_OK ){ if( op==0 ){ /* No entries will match. Set the "filtered-out" bit for all rows. */ memset(pBitmask->a, 0xFF, pBitmask->n); }else if( szInt==4 ){ const int *a = (const int*)&pMeta->a[VEC1_META_SZHDR]; vec1MetaFilterScan4ByteArray(a, pBitmask->a, nEntry, rval, aRval, op); }else{ assert( szInt==1 ); const u8 *a = (const u8*)&pMeta->a[VEC1_META_SZHDR]; vec1MetaFilterScan1ByteArray(a, pBitmask->a, nEntry, rval, aRval, op); } } sqlite3_free(aRval); } return rc; } static VEC1_NOINLINE int vec1DoMetaFilters( Vec1Tab *pTab, Vec1Query *pQuery, i64 iId, int nEntry, Vec1Buffer *pMeta, Vec1Buffer *pBitmask ){ int rc = SQLITE_OK; int ii; /* Grow and zero the bitmask array. */ int nByte = (nEntry+7)/8; pBitmask->n = 0; rc = vec1BufferGrow(pBitmask, nByte); if( rc==SQLITE_OK ){ memset(pBitmask->a, 0, nByte); } pBitmask->n = nByte; assert( pQuery->nFilter>0 ); for(ii=0; rc==SQLITE_OK && iinFilter; ii++){ Vec1Filter *pFilter = &pQuery->aFilter[ii]; VEC1_QINSTR_START(pTab, VEC1_QINSTR_METAREAD); rc = vec1ReadMeta(pTab, nEntry, pMeta, iId, pFilter->iMeta); VEC1_QINSTR_STOP(pTab, VEC1_QINSTR_METAREAD); VEC1_QINSTR_START(pTab, VEC1_QINSTR_METASCAN); if( rc==SQLITE_OK ){ int jj = 0; u32 f = vec1GetU32(pMeta->a); if( f & VEC1_META_1BYTEINT ){ /* Single-byte integer format */ rc = vec1MetaFilterIntList(pFilter, 1, nEntry, pMeta, pBitmask); }else if( f & VEC1_META_4BYTEINT ){ /* 4-byte integer format */ rc = vec1MetaFilterIntList(pFilter, 4, nEntry, pMeta, pBitmask); }else if( f & VEC1_META_REAL ){ Vec1MetaValue val = {0, 0, 0, 0}; for(jj=0; jja[VEC1_META_SZHDR+jj*sizeof_f64] ); if( iVal==VEC1_META_REALNULL ){ val.eType = SQLITE_NULL; }else{ val.eType = SQLITE_FLOAT; memcpy(&val.fVal, &iVal, sizeof(u64)); } if( vec1MetaValueFilter(pFilter, &val) ){ pBitmask->a[jj / 8] |= (1 << (jj % 8)); } } }else{ /* Generic format */ int iOff = VEC1_META_SZHDR; for(jj=0; jja[jj / 8] |= (1 << (jj % 8)); } } } } if( pFilter->vop==VEC1_OP_IN ){ ii += (int)pFilter->iVal; } VEC1_QINSTR_STOP(pTab, VEC1_QINSTR_METASCAN); } return rc; } static void vec1EncodeVectorBit( const Vec1Model *pMod, /* Model */ const float *aVec, /* Input vector */ u8 *aCode /* OUT: Encoded vector */ ){ const int nElem = (int)pMod->hdr.nElem; float norm; int ii; u8 *pOut; assert( pMod->hdr.flags & VEC1_MODEL_BITQUANT ); norm = (float)vec1VectorNorm2(aVec, nElem); norm = sqrtf(norm); memcpy(aCode, &norm, sizeof_f32); assert( pMod->nCodeElem==8 || pMod->nCodeElem==16 ); pOut = &aCode[4]; for(ii=0; iinCodeElem){ u8 v = 0; int jj; if( pMod->nCodeElem==8 ){ for(jj=0; jj<8; jj++){ if( aVec[ii+jj]>0.0 ){ v |= (1<0.0 ){ v |= (1<hdr.nCodebook; const int nCodeElem = pMod->nCodeElem; const float *pIn = pMod->aModelT; int M, K; for(M=0; Mhdr.nCodebook; const int nCodeElem = pMod->nCodeElem; int ii = 0; for(ii=0; iiaModel[ii*nCodeElem*VEC1_PQ_CODEBOOK_SZ]; const float *aSub = &aVec[ii*nCodeElem]; aOut[ii] = (u8)vec1BestMatchSimple( pCodebook, VEC1_PQ_CODEBOOK_SZ, aSub, nCodeElem, pfTotalDist ); } } static void vec1EncodeVector( const Vec1Model *pMod, const float *aVec, u8 *aCode ){ if( pMod->hdr.flags & VEC1_MODEL_BITQUANT ){ vec1EncodeVectorBit(pMod, aVec, aCode); }else{ vec1EncodeVectorPQ(pMod, aVec, aCode); } } /* ** Buffer aCode[] contains a vector quantized according to model pMod. ** This function (lossily) reconstructs the original vector and writes ** it to buffer aVec[] before returning. */ static void vec1DecodeVector( const Vec1Model *pMod, /* Model used to encode vector */ const u8 *aCode, /* Encoded vector */ float *aVec /* Populate this buffer with full vector */ ){ const int nElem = (int)pMod->hdr.nElem; if( pMod->hdr.flags & VEC1_MODEL_BITQUANT ){ float fNorm = 0.0; float fVal = 0.0; int ii; /* The first 4 bytes of the encoded vector store the norm of the vector, ** as a 32-bit float. */ memcpy(&fNorm, aCode, sizeof_f32); /* The reconstructed vector uses the following value as the magnitude ** of each dimension. The value is -ve if the corresponding bit is clear, ** or +ve if it is set. */ fVal = (float)sqrt((fNorm*fNorm) / nElem); assert( pMod->nCodeElem==8 || pMod->nCodeElem==16 ); for(ii=0; iinCodeElem==8 ){ if( aCode[sizeof_f32 + (ii/8)] & (1 << (ii%8)) ){ aVec[ii] = fVal; }else{ aVec[ii] = -1 * fVal; } }else{ if( aCode[sizeof_f32 + (ii/16)] & (1 << ((ii/2)%8)) ){ aVec[ii] = fVal; }else{ aVec[ii] = -1 * fVal; } } } }else{ int ii; for(ii=0; ii<(int)pMod->hdr.nCodebook; ii++){ const float *aCopy = &pMod->aModel[ (ii * VEC1_PQ_CODEBOOK_SZ + aCode[ii]) * pMod->nCodeElem ]; int nCopy = MIN(pMod->nCodeElem, nElem - ii*pMod->nCodeElem); memcpy(&aVec[ii * pMod->nCodeElem], aCopy, nCopy*sizeof_f32); } } } /* ** Return true if any value in aAcc[] is less than fMin. False otherwise. */ static int vec1AnyLessThan(const float aAcc[16], float fMin){ #if defined(VEC1_HAVE_AVX2) __m256 vMin = _mm256_set1_ps(fMin); __m256 cmp0 = _mm256_cmp_ps(_mm256_loadu_ps(aAcc), vMin, _CMP_LT_OQ); __m256 cmp1 = _mm256_cmp_ps(_mm256_loadu_ps(aAcc+8), vMin, _CMP_LT_OQ); __m256 mask = _mm256_or_ps(cmp0, cmp1); return _mm256_movemask_ps(mask)!=0; #elif defined(VEC1_HAVE_NEON) float32x4_t vMin = vdupq_n_f32(fMin); uint32x4_t cmp0 = vcltq_f32(vld1q_f32(aAcc), vMin); uint32x4_t cmp1 = vcltq_f32(vld1q_f32(aAcc+4), vMin); uint32x4_t cmp2 = vcltq_f32(vld1q_f32(aAcc+8), vMin); uint32x4_t cmp3 = vcltq_f32(vld1q_f32(aAcc+12), vMin); uint32x4_t mask = vorrq_u32(vorrq_u32(cmp0, cmp1), vorrq_u32(cmp2, cmp3)); return vmaxvq_u32(mask)!=0; #else int ii; for(ii=0; ii<16; ii++){ if( aAcc[ii]bStreaming==0 ){ iEarly = (nCodebook/4); } assert( nNorm==sizeof_f32 || nNorm==0 ); /* One iteration of this loop for each block in the blob. */ for(iOff=VEC1_LIST_SZHDR + nEntry*szRowid; iOff=iEarly && (iBook & 0x03)==0x03 && vec1AnyLessThan(aAcc, (float)pHeap->fMin)==0 ){ goto skip; } } if( nNorm ){ int jj; for(jj=0; jjfMin && (aMask==0 || (aMask[(iBlk*VEC1_PQ_BLOCKSIZE+ii)/8] & (1 << (ii%8)))==0) ){ i64 iRowid = 0; if( szRowid==4 ){ iRowid = vec1GetU32(&aRowid[ii * szRowid]); if( iRowid==VEC1_TOMBSTONE_32 ) continue; }else{ iRowid = vec1GetU64(&aRowid[ii * szRowid]); if( iRowid==VEC1_TOMBSTONE_64 ) continue; } vec1HeapInsert(pHeap, iRowid, dist); } } skip:; /* semi-colon required to avoid C23 warning/error */ } } static int vec1DoKANNBucket( Vec1Query *pQuery, int iBucket, Vec1Buffer *pDist, Vec1Buffer *pMeta, Vec1Buffer *pBitmask ){ Vec1Tab *pTab = pQuery->pTab; Vec1AnnHeap *pHeap = &pQuery->heap; const int nElem = pTab->cfg.nElem; const int bUseBQ = (pTab->mod.hdr.flags & VEC1_MODEL_BITQUANT) ? 1 : 0; const int nCodebook = (int)pTab->mod.hdr.nCodebook; const int bUsePQ = (nCodebook>0 && bUseBQ==0); sqlite3_stmt *pScanIdx = 0; int rc = SQLITE_OK; float *aDist = 0; float fResidualNorm2 = 0.0; if( iBucket<0 ){ rc = vec1GetSql(pTab, VEC1_SQL_SCAN_IDX, &pScanIdx); }else{ rc = vec1GetSql(pTab, VEC1_SCAN_BUCKET, &pScanIdx); if( rc==SQLITE_OK ){ sqlite3_bind_int(pScanIdx, 1, iBucket); } } assert( 0==pDist->n || 0==(pTab->mod.hdr.flags & VEC1_MODEL_RESIDUAL) ); if( (bUsePQ || bUseBQ) ){ /* If using PQ or BQ codes, build a LUT for this bucket. */ if( rc==SQLITE_OK && pDist->n==0 ){ const float *aEncode = pQuery->aTransform; int nByte = (bUsePQ ? vec1PQLUTSize(nCodebook) : /* Product quant */ vec1BinaryQuantSize(nElem) /* Binary quant */ ); rc = vec1BufferSize(pDist, nByte); if( rc!=SQLITE_OK ) return rc; aDist = (float*)pDist->a; VEC1_QINSTR_START(pTab, VEC1_QINSTR_LUT); if( pTab->mod.hdr.flags & VEC1_MODEL_RESIDUAL ){ float *aTmp = pTab->aTmpVec; assert( iBucket>=0 && iBucket<(int)pTab->mod.hdr.nBucket ); vec1Sub(aTmp, aEncode, &pTab->mod.aCentroid[iBucket*nElem], nElem); aEncode = aTmp; } if( bUsePQ ){ vec1PQLUTBuild(pTab, aEncode, aDist); }else{ vec1BinaryQuantLUTBuild(pTab, aEncode, aDist); fResidualNorm2 = (float)vec1VectorNorm2(aEncode, pTab->cfg.nElem); vec1EncodeVectorBit(&pTab->mod, aEncode, pQuery->aBit); } VEC1_QINSTR_STOP(pTab, VEC1_QINSTR_LUT); pDist->n = nByte; } aDist = (float*)pDist->a; } while( rc==SQLITE_OK ){ i64 iId = 0; int res; /* Load the next index entry from the %_idx table */ VEC1_QINSTR_START(pTab, VEC1_QINSTR_IDXREAD); res = sqlite3_step(pScanIdx); VEC1_QINSTR_STOP(pTab, VEC1_QINSTR_IDXREAD); if( res!=SQLITE_ROW ) break; const u8 *aBlob = (const u8*)sqlite3_column_blob(pScanIdx, 0); int nBlob = sqlite3_column_bytes(pScanIdx, 0); int nEntry = 0; int flags = 0; int ii; int szRowid = 4; if( vec1CheckIdxSize(pTab, aBlob, nBlob) ){ rc = VEC1_CORRUPT; break; } nEntry = vec1GetU32(&aBlob[4]); iId = sqlite3_column_int64(pScanIdx, 2); if( pQuery->nFilter>0 ){ rc = vec1DoMetaFilters(pTab, pQuery, iId, nEntry, pMeta, pBitmask); if( rc!=SQLITE_OK ) break; } /* Loop through each of the PQ vectors in aBlob[] */ VEC1_QINSTR_START(pTab, VEC1_QINSTR_PQSCAN); flags = vec1GetU32(&aBlob[0]); if( flags & VEC1_LIST_64BIT ) szRowid = 8; if( aDist ){ vec1ScanBlocked( pHeap, pBitmask->a, aDist, nCodebook, aBlob, nBlob, szRowid, nEntry, (bUseBQ ? sizeof_f32 : 0), fResidualNorm2 ); }else{ const int szVec = sizeof_f32*nElem; assert( bUseBQ==0 && bUsePQ==0 ); for(ii=0; iin>0 && (pBitmask->a[ii/8] & (1 << (ii % 8))) ){ continue; } if( szRowid==4 ){ iRowid = vec1GetU32(&aBlob[iRowidOff]); if( iRowid==VEC1_TOMBSTONE_32 ) continue; }else{ iRowid = vec1GetU64(&aBlob[iRowidOff]); if( iRowid==VEC1_TOMBSTONE_64 ) continue; } if( pTab->mod.hdr.eDistance==VEC1_DISTANCE_COS ){ fDist = vec1CosDist(pQuery->aVector, aFull, nElem); }else{ fDist = vec1L2Dist(pQuery->aVector, aFull, nElem); } vec1HeapInsert(pHeap, iRowid, fDist); } } VEC1_QINSTR_STOP(pTab, VEC1_QINSTR_PQSCAN); } vec1StmtReset(&rc, pScanIdx); if( rc==SQLITE_OK ) rc = pHeap->rc; return rc; } static int vec1DoKANNQuery(Vec1Csr *pCsr){ int rc = SQLITE_OK; Vec1Tab *pTab = (Vec1Tab*)(pCsr->base.pVtab); Vec1Query *pQuery = pCsr->pQuery; Vec1AnnHeap *pHeap = &pQuery->heap; /* Table of distances used for ANN queries against the PQ quantized ** vectors in the %_idx table. This is an array of (nCodebook * 256) ** float values. The 256 distances for each codebook are stored ** contiguously, so to lookup code iCode in codebook iBook: ** ** aDist[(iBook * 256) + iCode] */ Vec1BucketResult *aBucket = 0; const float *aVector = pQuery->aVector; int iProbe = 0; Vec1Buffer meta = {0, 0, 0}; /* Buffer to load meta arrays */ Vec1Buffer bitmask = {0, 0, 0}; /* Buffer for meta filter bitmask */ Vec1Buffer dist = {0, 0, 0}; /* Buffer for LUT */ VEC1_QINSTR_START(pTab, VEC1_QINSTR_INITQUERY); /* Transform the input vector if required. If no transformation is ** required, then pQuery->aTransform already points to the same buffer ** as pQuery->aVector. */ assert( vec1TransformRequired(&pTab->mod)==(pQuery->aTransform!=aVector) ); vec1TransformInputVector(&pTab->mod, pQuery->aTransform, aVector); rc = vec1HeapInit(pHeap, pQuery->K, pQuery->bStreaming); VEC1_QINSTR_STOP(pTab, VEC1_QINSTR_INITQUERY); /* Do the coarse quantizer step. If nbucket is not 0, this block allocates ** the aBucket[] array. The first nProbe elements of which are the ones ** that should be scanned by this call. The others may be used later if ** this is a streaming query, but are usually ignored. */ VEC1_QINSTR_START(pTab, VEC1_QINSTR_COARSE); if( rc==SQLITE_OK ){ if( pTab->mod.hdr.nBucket==0 ){ assert( pQuery->nProbe==1 ); }else{ assert( pQuery->nProbe>=1 && pQuery->nProbe<=(int)pTab->mod.hdr.nBucket ); rc = vec1FindBuckets(pTab, pQuery->aTransform, pQuery->nProbe, &aBucket); } } VEC1_QINSTR_STOP(pTab, VEC1_QINSTR_COARSE); for(iProbe=0; iProbenProbe && rc==SQLITE_OK; iProbe++){ int iBucket = -1; if( pTab->mod.hdr.nBucket>0 ){ iBucket = aBucket[iProbe].iBucket; if( pQuery->nProbeSlack>0.0 && aBucket[iProbe].fDist>aBucket[0].fDist*(1.0+pQuery->nProbeSlack) ){ break; } } if( pTab->mod.hdr.flags & VEC1_MODEL_RESIDUAL ){ dist.n = 0; } rc = vec1DoKANNBucket(pQuery, iBucket, &dist, &meta, &bitmask); } VEC1_QINSTR_START(pTab, VEC1_QINSTR_FINALSORT); if( rc==SQLITE_OK ){ i64 nSort = MIN( pHeap->nRes, pQuery->K ); if( pQuery->bStreaming ){ /* For a streaming query, set the pQuery->aBucket[] array to contain ** the remaining buckets - the ones that have not yet been scanned. */ pQuery->nBucket = pTab->mod.hdr.nBucket - iProbe; if( pQuery->nBucket>0 ){ int nCopy = sizeof(Vec1BucketResult) * pQuery->nBucket; memmove(aBucket, &aBucket[iProbe], nCopy); pQuery->aBucket = aBucket; aBucket = 0; } pQuery->nOrigRes = (int)MAX(16, pHeap->nRes); } /* For a streaming query, all candidate vectors were added to the ** aRes[] array. The heap has guaranteed that the first nSort elements ** of the Vec1AnnHeap.aRes[] array have distances <= than the remaining ** elements. So it is still correct just to sort the first nSort ** elements of aRes[] and set nCurrentRes=nSort to configure the first ** block of rows to return. */ rc = vec1AnnResultSort(pHeap->aRes, nSort); pCsr->nCurrentRes = (int)nSort; } sqlite3_free(aBucket); vec1BufferFree(&meta); vec1BufferFree(&bitmask); vec1BufferFree(&dist); VEC1_QINSTR_STOP(pTab, VEC1_QINSTR_FINALSORT); return rc; } /* ** Load the contents of the %_config table into the pTab->cfg structure. ** if the iModelVersion field has changed since %_config was last loaded, ** also load the model from the %_model table. */ static int vec1LoadConfig(Vec1Tab *pTab){ sqlite3_stmt *pStmt = 0; int rc = SQLITE_OK; int nReq = 0; pTab->cfg.nBlocksize = VEC1_BLOCKSIZE_DEFAULT; pTab->cfg.nBlocksizeMin = VEC1_BLOCKSIZE_MIN_DEFAULT; pTab->cfg.nElem = 0; rc = vec1GetSql(pTab, VEC1_SQL_LOAD_CONFIG, &pStmt); if( rc==SQLITE_OK ){ while( sqlite3_step(pStmt)==SQLITE_ROW ){ int iField = sqlite3_column_int(pStmt, 0); switch( iField ){ case VEC1_CONFIG_MODEL: { i64 iNew = sqlite3_column_int64(pStmt, 1); if( pTab->cfg.iModelVersion!=iNew ){ rc = vec1LoadModel(pTab); if( rc==SQLITE_OK ){ pTab->cfg.iModelVersion = iNew; } } break; } case VEC1_CONFIG_BLOCKSIZE: { pTab->cfg.nBlocksize = sqlite3_column_int(pStmt, 1); pTab->cfg.nBlocksize = MAX(VEC1_BLOCKSIZE_MIN, pTab->cfg.nBlocksize); pTab->cfg.nBlocksize = MIN(VEC1_BLOCKSIZE_MAX, pTab->cfg.nBlocksize); break; } case VEC1_CONFIG_BLOCKSIZE_MIN: { int nMin = sqlite3_column_int(pStmt, 1); nMin = MAX(VEC1_BLOCKSIZE_MIN, nMin); nMin = MIN(VEC1_BLOCKSIZE_MAX, nMin); pTab->cfg.nBlocksizeMin = nMin; break; } case VEC1_CONFIG_NELEM: { pTab->cfg.nElem = sqlite3_column_int(pStmt, 1); break; } case VEC1_CONFIG_FMTVERSION: { int iVersion = sqlite3_column_int(pStmt, 1); if( iVersionVEC1_CURRENT_FMTVERSION ){ vec1VtabError(pTab, "unsupported index version - requires vec1 %d.%d", (iVersion / 1000), (iVersion % 1000) ); rc = SQLITE_ERROR; } break; } default: { vec1VtabError(pTab, "unrecognized config entry: %d", iField); rc = SQLITE_ERROR; break; } } } vec1StmtReset(&rc, pStmt); } nReq = MAX(pTab->mod.hdr.nElem, pTab->mod.nCodeElem*pTab->mod.hdr.nCodebook); if( (rc==SQLITE_OK) && nReq>0 && nReq!=pTab->nTmpVec ){ sqlite3_free(pTab->aTmpVec); pTab->aTmpVec = (float*)vec1MallocZero(nReq * sizeof_f32); if( pTab->aTmpVec ){ pTab->nTmpVec = nReq; }else{ pTab->nTmpVec = 0; rc = SQLITE_NOMEM; } } if( rc==SQLITE_OK && pTab->mod.hdr.nElem!=0 && (u32)pTab->cfg.nElem!=pTab->mod.hdr.nElem ){ vec1VtabError(pTab, "model/data vector size mismatch: %d/%d", pTab->mod.hdr.nElem, pTab->cfg.nElem ); vec1UnloadModel(pTab); rc = SQLITE_ERROR; } return rc; } /* ** Load the configuration if it is not already loaded. The configuration ** is assumed not to be loaded if cfg.nElem==0. ** ** Return SQLITE_OK if successful, or an SQLite error code otherwise. If ** an error occurs, this function may leave an error message in the virtual ** table object. */ static int vec1LoadConfigIfRequired(Vec1Tab *pTab){ int rc = SQLITE_OK; if( pTab->cfg.nElem==0 ){ rc = vec1LoadConfig(pTab); if( rc!=SQLITE_OK ){ pTab->cfg.nElem = 0; } } return rc; } static int vec1InterpretNProbe(Vec1Tab *pTab, double fVal){ int nProbe; if( fVal>=1.0 ){ nProbe = (int)fVal; }else{ nProbe = (int)(fVal * pTab->mod.hdr.nBucket); } nProbe = MIN(nProbe, (int)pTab->mod.hdr.nBucket); nProbe = MAX(nProbe, 1); return nProbe; } static int vec1ParseQueryParamCb( void *pCtx, const char *zOpt, int eType, i64 iVal, double fVal, const char *zVal, char **pzErr ){ Vec1Query *pQuery = (Vec1Query*)pCtx; const char *azOpt[] = { "nprobe", /* 0 */ "K", /* 1 */ "streaming", /* 2 */ "nprobe_slack", /* 3 */ 0 }; int iOpt = 0; UNUSED_PARAMETER2(iVal, eType); UNUSED_PARAMETER2(pzErr, zVal); int rc = vec1ConfigEnum(&iOpt, zOpt, azOpt, "option", pzErr); if( rc==SQLITE_OK ){ switch( iOpt ){ case 0: assert( sqlite3_stricmp("nprobe", zOpt)==0 ); if( fVal<=0.0 ){ *pzErr = sqlite3_mprintf( "vec1: nprobe must be a numeric value greater than 0.0" ); rc = SQLITE_ERROR; } pQuery->nProbe = vec1InterpretNProbe(pQuery->pTab, fVal); break; case 1: assert( sqlite3_stricmp("K", zOpt)==0 ); if( eType==SQLITE_INTEGER ){ pQuery->K = iVal; }else{ pQuery->K = -1; } break; case 2: assert( sqlite3_stricmp("streaming", zOpt)==0 ); pQuery->bStreaming = ((int)fVal) ? 1 : 0; break; default: assert( sqlite3_stricmp("nprobe_slack", zOpt)==0 ); pQuery->nProbeSlack = fVal; break; } } return rc; } static int vec1ModelIsFlat(Vec1Model *pMod){ return ((pMod->hdr.flags & VEC1_MODEL_INDEX) && pMod->hdr.nCodebook==0); } static const char aHexDigit[] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f', }; static int vec1DecodeMetaIdx(const char *z){ int iRet = 0; assert( (z[0]>='0' && z[0]<='9') || (z[0]>='a' && z[0]<='f') ); assert( (z[1]>='0' && z[1]<='9') || (z[1]>='a' && z[1]<='f') ); if( z[0]<='9' ){ iRet = (int)(z[0] - '0'); }else{ iRet = (int)(10 + z[0] - 'a'); } iRet = iRet << 4; if( z[1]<='9' ){ iRet |= (int)(z[1] - '0'); }else{ iRet |= (int)(10 + z[1] - 'a'); } return iRet; } /* ** This is invoked when a value of type eType (which is not SQLITE_BLOB) ** has been passed to vec1 as a vector. This is an error. Configure the ** table with an error message and return SQLITE_ERROR. */ static int vec1VectorTypeError(Vec1Tab *pTab, int eType){ const char *azType[] = {0, "INTEGER", "FLOAT", "TEXT", "BLOB", "NULL"}; assert( eType!=SQLITE_BLOB && eType>=1 && eType<=5 ); vec1VtabError(pTab, "vec1: unexpected vector type %s, expected BLOB", azType[eType] ); return SQLITE_ERROR; } /* ** This is invoked when a blob nHave bytes in size has been passed to vec1 ** as a vector when a (pTab->cfg.nElem*sizeof_f32) blob was expected. ** Configure the table with an error message and return SQLITE_ERROR. */ static int vec1VectorSizeError(Vec1Tab *pTab, int nHave){ assert( pTab->cfg.nElem>0 && (pTab->cfg.nElem*sizeof_f32)!=nHave ); vec1VtabError(pTab, "vec1: unexpected vector blob size %d bytes, expected %d", nHave, pTab->cfg.nElem*sizeof_f32 ); return SQLITE_ERROR; } static int vec1FilterArraySize( const char *idxStr, sqlite3_value **argv, int *pnFilter ){ int ii; int nFilter = 0; int iArg = 1; int rc = SQLITE_OK; for(ii=0; idxStr[ii]; ii++){ char c = idxStr[ii]; if( c==VEC1_OP_IN ){ sqlite3_value *pDummy = 0; sqlite3_value *pList = argv[iArg]; nFilter++; for(rc=sqlite3_vtab_in_first(pList, &pDummy); rc==SQLITE_OK; rc=sqlite3_vtab_in_next(pList, &pDummy) ){ assert( pDummy!=0 ); nFilter++; } if( rc==SQLITE_DONE ) rc = SQLITE_OK; ii += 2; }else if( c!=VEC1_OP_PARAMS && c!=VEC1_OP_LIMIT ){ ii += 2; nFilter++; } iArg++; } *pnFilter = nFilter; return rc; } static void *vec1Dup(int *pRc, const void *p, int n){ void *pRet = 0; assert( *pRc==SQLITE_OK ); if( p==0 ){ *pRc = SQLITE_NOMEM; }else{ pRet = vec1MallocZero(n+1); if( pRet==0 ){ *pRc = SQLITE_NOMEM; }else if( n>0 ){ memcpy(pRet, p, n); } } return pRet; } static void vec1ValueToFilter( int *pRc, sqlite3_value *pVal, Vec1Filter *pFilter ){ pFilter->eType = sqlite3_value_type(pVal); switch( pFilter->eType ){ case SQLITE_NULL: break; case SQLITE_INTEGER: pFilter->iVal = sqlite3_value_int64(pVal); break; case SQLITE_FLOAT: pFilter->fVal = sqlite3_value_double(pVal); break; case SQLITE_TEXT: pFilter->iVal = sqlite3_value_bytes(pVal); pFilter->pPtr = (u8*)vec1Dup( pRc, sqlite3_value_text(pVal), (int)pFilter->iVal ); break; default: { int nByte = sqlite3_value_bytes(pVal); assert( pFilter->eType==SQLITE_BLOB ); pFilter->iVal = (i64)nByte; pFilter->pPtr = (u8*)vec1Dup( pRc, nByte? sqlite3_value_blob(pVal) : (const void*)"", nByte ); break; } } } /* ** Return true if value pVal contains an integer - either an SQL integer, ** text that looks like an integer, or a double that can be converted ** to an integer without loss of data. */ static int vecIsIntegerValue(sqlite3_value *pVal){ if( sqlite3_value_numeric_type(pVal)==SQLITE_INTEGER ) return 1; if( sqlite3_value_numeric_type(pVal)==SQLITE_FLOAT ){ double fVal = sqlite3_value_double(pVal); if( fVal==(double)((i64)fVal) ) return 1; } return 0; } static int vec1SetupKANNQuery( Vec1Tab *pTab, const char *idxStr, /* idxStr passed to xFilter */ sqlite3_value **argv, /* Parameter values passed to xFilter */ Vec1Query **ppOut /* OUT: Query object */ ){ const int nElem = pTab->cfg.nElem; const int nSpace = MAX(pTab->cfg.nElem, pTab->nTmpVec); int nMax = 0; /* Max possible meta-data filters */ int nVec = sqlite3_value_bytes(argv[0]); const u8 *aVec = sqlite3_value_blob(argv[0]); int iArg = 1; int iIdxStr = 0; int rc = SQLITE_OK; Vec1Query *p = 0; int bTransform = vec1TransformRequired(&pTab->mod); int nBit = 0; if( pTab->mod.hdr.flags & VEC1_MODEL_BITQUANT ){ nBit = sizeof_f32 + pTab->mod.hdr.nCodebook; } /* Determine the required size of the Vec1Query.aFilter[] array. */ rc = vec1FilterArraySize(idxStr, argv, &nMax); p = (Vec1Query*)vec1MallocZero( sizeof(Vec1Query) /* Vec1Query object itself */ + nMax * sizeof(Vec1Filter) /* Vec1Query.aFilter[] */ + nSpace * sizeof_f32 /* Vec1Query.aVector[] */ + bTransform * (nSpace * sizeof_f32) /* aTransform[] */ + nBit /* aBit[] */ ); if( p==0 ) return SQLITE_NOMEM; p->aFilter = (Vec1Filter*)&p[1]; p->aVector = (float*)&p->aFilter[nMax]; if( bTransform ){ p->aTransform = &p->aVector[nSpace]; }else{ p->aTransform = p->aVector; } if( nBit ){ p->aBit = (u8*)&p->aTransform[nSpace]; } /* Initialize default query parameters. ** ** Use VEC1_SMALLEST_INT64 to signify that no K value has been supplied ** If somebody specifies this value via a JSON query parameter, they ** may get a slightly inaccurate error message. So be it. */ p->pTab = pTab; p->K = VEC1_SMALLEST_INT64; p->nProbe = vec1InterpretNProbe(pTab, pTab->pTabList->nProbeArg); p->bStreaming = 0; /* Check the query vector is the right size and copy it into the ** aVector[] buffer. ** ** Omit this check if nElem==0. This occurs when the table has not yet ** received the first vector. So carry on checking the remainder of the ** parameters, but don't worry about checking the input vector or ** copying it into aVector[], as the query will return zero rows in ** any case. */ if( nElem!=0 ){ if( nVec!=(nElem*sizeof_f32) ){ rc = vec1VectorSizeError(pTab, nVec); }else{ memcpy(p->aVector, aVec, nVec); } } /* Check that the query vector does not contain any NaN or Infinity values */ if( rc==SQLITE_OK ){ rc = vec1VectorIsNonFinite((float*)aVec, (nVec/sizeof_f32)); if( rc!=SQLITE_OK ){ vec1VtabError(pTab, "vec1: non-finite value not allowed in vector"); } } /* Check if parameters where supplied via hidden column "arg". If so, ** the first character of idxStr is 'P' - parameters. */ if( rc==SQLITE_OK && idxStr[0]==VEC1_OP_PARAMS ){ sqlite3_value *pVal = argv[iArg++]; if( vecIsIntegerValue(pVal) ){ p->K = sqlite3_value_int64(pVal); }else if( sqlite3_value_type(pVal)==SQLITE_TEXT ){ assert( pTab->base.zErrMsg==0 ); rc = vec1ParseJsonConfig(pTab->db, (const char*)sqlite3_value_text(pVal), vec1ParseQueryParamCb, (void*)p, &pTab->base.zErrMsg ); }else{ p->K = -1; } iIdxStr = 1; } /* Process remaining constraints, if any. */ if( rc==SQLITE_OK && idxStr[iIdxStr]!='\0' ){ char c; while( (c = idxStr[iIdxStr++])!='\0' ){ sqlite3_value *pVal = argv[iArg++]; if( c==VEC1_OP_LIMIT ){ int iVal = sqlite3_value_int(pVal); if( iVal>=0 && (p->K==VEC1_SMALLEST_INT64 || p->K>0) ){ /* A non-negative LIMIT clause. If no K value was specified, use ** this value as K. Or, if a K value was specified, use the ** minimum of K and this value. */ p->K = ((p->K < 0) ? iVal : MIN(p->K, iVal)); } }else{ /* Filter on a meta value. The operand character is followed by ** 2 hexadecimal digits encoding the index of the meta column ** on the LHS of the constraint. */ Vec1Filter *pFilter = &p->aFilter[p->nFilter++]; pFilter->vop = c; pFilter->iMeta = vec1DecodeMetaIdx(&idxStr[iIdxStr]); iIdxStr += 2; if( c==VEC1_OP_IN ){ sqlite3_value *pInVal = 0; assert( pFilter->iVal==0 ); for(rc=sqlite3_vtab_in_first(pVal, &pInVal); rc==SQLITE_OK; rc=sqlite3_vtab_in_next(pVal, &pInVal) ){ Vec1Filter *pInFilter = &p->aFilter[p->nFilter++]; vec1ValueToFilter(&rc, pInVal, pInFilter); if( rc!=SQLITE_OK ) break; pInFilter->vop = VEC1_OP_EQ; pInFilter->iMeta = pFilter->iMeta; pFilter->iVal++; } if( rc==SQLITE_DONE ) rc = SQLITE_OK; }else if( c==VEC1_OP_NOTNULL ){ pFilter->eType = SQLITE_BLOB; pFilter->iVal = 0; }else if( c==VEC1_OP_ISNULL ){ pFilter->eType = SQLITE_NULL; pFilter->vop = VEC1_OP_IS; }else{ vec1ValueToFilter(&rc, pVal, pFilter); } } } } assert( rc!=SQLITE_OK || p->nFilter==nMax ); /* Check that a usable K value has been specified. Either explicitly or ** via a visible LIMIT clause. */ if( rc==SQLITE_OK ){ if( p->K==VEC1_SMALLEST_INT64 ){ vec1VtabError(pTab, "vec1: no K value or visible LIMIT clause"); rc = SQLITE_ERROR; }else if( p->K<0 || p->K>VEC1_MAX_K ){ vec1VtabError(pTab, "vec1: K must be an integer value between 0 and %d", VEC1_MAX_K ); rc = SQLITE_ERROR; } } /* If an error occurred, free any dynamic allocations and zero the output ** structure before returning. Otherwise, sort the aFilter[] array by ** iMeta value. */ if( rc!=SQLITE_OK ){ vec1QueryFree(p); p = 0; }else if( 0 ){ /* Sort aFilter[] by Vec1Filter.iMeta value. The sort must be stable, ** in order to avoid disrupting the arguments to a VEC1_OP_IN filter. */ int i1, i2; for(i1=0; i1nFilter; i1++){ Vec1Filter tmp = p->aFilter[i1]; for(i2=i1-1; i2>=0 && p->aFilter[i2].iMeta>tmp.iMeta; i2--){ p->aFilter[i2+1] = p->aFilter[i2]; } p->aFilter[i2+1] = tmp; } #ifndef NDEBUG /* Assert() that the sort worked. */ for(i1=1; i1nFilter; i1++){ assert( p->aFilter[i1-1].iMeta<=p->aFilter[i1].iMeta ); } #endif } *ppOut = p; return rc; } /* ** This function is called in index:"none" mode. */ static int vec1QueryToSql(Vec1Csr *pCsr){ Vec1Tab *pTab = (Vec1Tab*)(pCsr->base.pVtab); Vec1Query *pQuery = pCsr->pQuery; int ii; int rc = SQLITE_OK; char *zWhere = 0; int iNextVar = 1; sqlite3_str *pWhere = sqlite3_str_new(pTab->db); const char *zAnd = ""; for(ii=0; iinFilter; ii++){ Vec1Filter *p = &pQuery->aFilter[ii]; int bRhs = 1; const char *zOp = ""; if( p->vop==VEC1_OP_IN ){ int jj; sqlite3_str_appendf(pWhere, "%sc%d IN(?%d", zAnd, p->iMeta, iNextVar++); for(jj=2; jj<=p->iVal; jj++){ sqlite3_str_appendf(pWhere, ", ?%d", iNextVar++); } ii += (int)p->iVal; sqlite3_str_appendf(pWhere, ")"); }else{ switch( p->vop ){ case VEC1_OP_EQ: zOp = "="; break; case VEC1_OP_LT: zOp = "<"; break; case VEC1_OP_GT: zOp = ">"; break; case VEC1_OP_LE: zOp = "<="; break; case VEC1_OP_GE: zOp = ">="; break; case VEC1_OP_IS: zOp = "IS"; break; default: assert( p->vop==VEC1_OP_NOTNULL ); zOp = "IS NOT NULL"; bRhs = 0; break; } sqlite3_str_appendf(pWhere, "%sc%d %s", zAnd, p->iMeta, zOp); if( bRhs ){ sqlite3_str_appendf(pWhere, " ?%d", iNextVar++); } } zAnd = " AND "; } rc = sqlite3_str_errcode(pWhere); zWhere = sqlite3_str_finish(pWhere); if( rc==SQLITE_OK ){ const char *zDistance = "vec1_l2_distance"; if( pTab->mod.hdr.eDistance==VEC1_DISTANCE_COS ){ zDistance = "vec1_cos_distance"; } rc = vec1PrepareSql(pTab, &pCsr->pStmt, "SELECT *, %s(?%d, vector) AS d FROM %Q.'%q_base'%s%s " "ORDER BY d LIMIT %lld", zDistance, iNextVar, pTab->zDb, pTab->zName, zWhere ? " WHERE " : "", zWhere, pQuery->K ); } if( rc==SQLITE_OK ){ int n = pTab->cfg.nElem * sizeof_f32; rc = sqlite3_bind_blob( pCsr->pStmt, iNextVar, pQuery->aVector, n, SQLITE_TRANSIENT ); iNextVar = 1; for(ii=0; rc==SQLITE_OK && iinFilter; ii++){ Vec1Filter *p = &pQuery->aFilter[ii]; if( p->vop!=VEC1_OP_IN && p->vop!=VEC1_OP_NOTNULL ){ switch( p->eType ){ case SQLITE_NULL: break; case SQLITE_INTEGER: sqlite3_bind_int64(pCsr->pStmt, iNextVar, p->iVal); break; case SQLITE_FLOAT: sqlite3_bind_double(pCsr->pStmt, iNextVar, p->fVal); break; case SQLITE_TEXT: rc = sqlite3_bind_text(pCsr->pStmt, iNextVar, (const char*)p->pPtr, (int)p->iVal, SQLITE_TRANSIENT ); break; default: assert( p->eType==SQLITE_BLOB ); rc = sqlite3_bind_blob( pCsr->pStmt, iNextVar, p->pPtr, (int)p->iVal, SQLITE_TRANSIENT ); break; } iNextVar++; } } } vec1QueryFree(pQuery); pCsr->pQuery = 0; pCsr->bSqlNNQuery = 1; sqlite3_free(zWhere); return rc; } /* ** There are three basic query plans supported by xBestIndex: ** ** 0: Full table scan. ** 1: Lookup by rowid. ** 2: K-nearest-neighbours search. */ static int vec1FilterMethod( sqlite3_vtab_cursor *cur, int idxNum, const char *idxStr, int argc, sqlite3_value **argv ){ Vec1Csr *pCsr = (Vec1Csr *)cur; Vec1Tab *pTab = (Vec1Tab*)(cur->pVtab); int rc = SQLITE_OK; UNUSED_PARAMETER(argc); assert( ((idxNum==0 || idxNum==1) && argc==0) || ((idxNum==2 || idxNum==3) && argc==1) || (idxNum==5 && argc>=1) ); vec1ResetCsr(pCsr); if( pCsr->bLoadConfig==0 && (idxNum & 0x01) ){ rc = vec1LoadConfig(pTab); if( rc!=SQLITE_OK ) return rc; pCsr->bLoadConfig = 1; } if( idxNum==0 || idxNum==1 ){ /* Full-table scan. */ rc = vec1PrepareSql(pTab, &pCsr->pStmt, "SELECT * FROM %Q.'%q_base'", pTab->zDb, pTab->zName ); }else if( idxNum==2 || idxNum==3 ){ /* Lookup by rowid */ rc = vec1PrepareSql(pTab, &pCsr->pStmt, "SELECT * FROM %Q.'%q_base' WHERE rowid=?", pTab->zDb, pTab->zName ); if( rc==SQLITE_OK ){ sqlite3_bind_value(pCsr->pStmt, 1, argv[0]); } }else{ /* ANN query */ Vec1Query *pQuery = 0; assert( idxNum==5 ); VEC1_QINSTR_START(pTab, VEC1_QINSTR_TOTAL); /* Parse the query parameters. */ rc = vec1SetupKANNQuery(pTab, idxStr, argv, &pQuery); pCsr->pQuery = pQuery; if( rc==SQLITE_OK ){ if( (pTab->mod.hdr.flags & VEC1_MODEL_INDEX)==0 ){ rc = vec1QueryToSql(pCsr); assert( pCsr->pQuery==0 ); assert( pCsr->bSqlNNQuery ); }else{ rc = vec1PrepareSql(pTab, &pCsr->pStmt, "SELECT * FROM %Q.'%q_base' WHERE id=?", pTab->zDb, pTab->zName ); if( rc==SQLITE_OK ){ rc = vec1DoKANNQuery(pCsr); pCsr->iCurrentRes = -1; } } } VEC1_QINSTR_STOP(pTab, VEC1_QINSTR_TOTAL); } if( rc==SQLITE_OK ){ rc = vec1NextMethod(cur); } return rc; } static int vec1EofMethod(sqlite3_vtab_cursor *cur){ Vec1Csr *pCur = (Vec1Csr *)cur; return (pCur->pStmt==0); } static int vec1SeekCsr(Vec1Csr *pCsr){ int rc = SQLITE_OK; if( pCsr->pQuery && pCsr->bSeek==0 ){ i64 iRowid = pCsr->pQuery->heap.aRes[pCsr->iCurrentRes].iRowid; sqlite3_bind_int64(pCsr->pStmt, 1, iRowid); if( SQLITE_ROW!=sqlite3_step(pCsr->pStmt) ){ rc = sqlite3_finalize(pCsr->pStmt); if( rc==SQLITE_OK ){ rc = VEC1_CORRUPT; } pCsr->pStmt = 0; } pCsr->bSeek = 1; } return rc; } typedef struct Vec1RowidLocation Vec1RowidLocation; struct Vec1RowidLocation { int iEntry; int nEntry; int nTombstone; int szRowid; Vec1ListBuilder *pList; sqlite3_blob *pBlob; i64 iIdx; }; static int vec1FindByRowid( Vec1Tab *pTab, int iBucket, /* Row belongs to this bucket */ int bWrite, /* Use writable blob-handle */ i64 iRowid, /* Rowid to locate */ Vec1RowidLocation *pOut /* OUT: Result */ ){ int rc = SQLITE_OK; Vec1Buffer buf = {0, 0, 0}; sqlite3_stmt *pSearch = 0; /* For looping through index entries */ sqlite3_blob *pBlob = 0; assert( (pTab->mod.hdr.flags & VEC1_MODEL_INDEX) ); memset(pOut, 0, sizeof(*pOut)); /* Check to see if the index entry for this vector is currently ** in main memory. */ if( pTab->pWriter ){ Vec1ListBuilder *pBld = &pTab->pWriter->aBld[iBucket]; int ii; for(ii=0; iibufRowid.n; ii+=pBld->szRowid){ i64 iRead; if( pBld->szRowid==4 ){ iRead = vec1GetU32(&pBld->bufRowid.a[ii]); }else{ iRead = vec1GetU64(&pBld->bufRowid.a[ii]); } if( iRead==iRowid ){ pOut->iEntry = (ii / pBld->szRowid); pOut->nEntry = (pBld->bufRowid.n / pBld->szRowid); pOut->nTombstone = pBld->nTombstone; pOut->szRowid = pBld->szRowid; pOut->pList = pBld; return SQLITE_OK; } } } rc = vec1GetSql(pTab, VEC1_SQL_SEARCH_IDX, &pSearch); if( rc==SQLITE_OK ){ sqlite3_bind_int(pSearch, 1, iBucket); sqlite3_bind_int64(pSearch, 2, iRowid); } while( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pSearch) ){ int nEntry = 0; u32 nTombstone = 0; u32 flags = 0; int szRowid = 4; i64 iIdx = sqlite3_column_int64(pSearch, 0); rc = sqlite3_blob_open( pTab->db, pTab->zDb, pTab->zIdxTbl, "val", iIdx, bWrite, &pBlob ); if( rc==SQLITE_OK ){ u8 a[VEC1_LIST_SZHDR]; rc = sqlite3_blob_read(pBlob, a, VEC1_LIST_SZHDR, 0); flags = vec1GetU32(&a[0]); nEntry = (int)vec1GetU32(&a[4]); nTombstone = vec1GetU32(&a[8]); if( flags & VEC1_LIST_64BIT ) szRowid = 8; } /* Now scan through the array of rowids at the start of the %_idx entry ** to find the rowid to be deleted. But, do not do this if the rowid ** happens to have the same value as the tombstone for this %_idx entry. */ if( (szRowid==4 && iRowid!=VEC1_TOMBSTONE_32) || (szRowid==8 && iRowid!=VEC1_TOMBSTONE_64) ){ if( rc==SQLITE_OK ){ buf.n = 0; rc = vec1BufferGrow(&buf, VEC1_LIST_SZHDR+szRowid*nEntry); } if( rc==SQLITE_OK ){ rc = sqlite3_blob_read( pBlob, &buf.a[VEC1_LIST_SZHDR], nEntry*szRowid, VEC1_LIST_SZHDR ); } if( rc==SQLITE_OK ){ int ii; for(ii=0; iiiEntry = ii; pOut->nEntry = nEntry; pOut->nTombstone = nTombstone; pOut->szRowid = szRowid; pOut->pBlob = pBlob; pOut->iIdx = iIdx; pBlob = 0; goto findbyrowid_done; } } } } (void)sqlite3_blob_close(pBlob); } findbyrowid_done: vec1StmtReset(&rc, pSearch); vec1BufferFree(&buf); return rc; } /* ** Obtain the full vector associated with the current row of cursor pCsr. */ static int vec1GetVector( Vec1Csr *pCsr, const u8 **paVec, /* OUT: Pointer to vector buffer */ int *pnVec, /* OUT: Size of (*paVec) in bytes */ u8 **pFree /* OUT: Buffer for caller to free (or NULL) */ ){ int rc = SQLITE_OK; Vec1Tab *pTab = (Vec1Tab*)(pCsr->base.pVtab); rc = vec1SeekCsr(pCsr); if( rc==SQLITE_OK ){ if( vec1ModelIsFlat(&pTab->mod) ){ i64 iRowid = sqlite3_column_int64(pCsr->pStmt, 0); int iBucket = sqlite3_column_int(pCsr->pStmt, 1); Vec1RowidLocation loc; rc = vec1FindByRowid(pTab, iBucket, 0, iRowid, &loc); if( rc==SQLITE_OK ){ int nVec = sizeof_f32 * pTab->cfg.nElem; u8 *aVec = (u8*)sqlite3_malloc(nVec); if( aVec==0 ){ rc = SQLITE_NOMEM; }else if( loc.pList ){ memcpy(aVec, &loc.pList->bufData.a[nVec * loc.iEntry], nVec); }else if( loc.pBlob ){ rc = sqlite3_blob_read(loc.pBlob, aVec, nVec, VEC1_LIST_SZHDR + (loc.nEntry*loc.szRowid) + (loc.iEntry * nVec) ); }else{ sqlite3_free(aVec); nVec = 0; aVec = 0; } if( rc!=SQLITE_OK ){ sqlite3_free(aVec); *paVec = 0; *pnVec = 0; *pFree = 0; }else{ *paVec = *pFree = aVec; *pnVec = nVec; } (void)sqlite3_blob_close(loc.pBlob); } }else{ *paVec = sqlite3_column_blob(pCsr->pStmt, 1); *pnVec = sqlite3_column_bytes(pCsr->pStmt, 1); *pFree = 0; } } return rc; } /* ** This function quantizes vector aIn[], then dequantizes it and finds ** the squared L2 distance between the original and the round-tripped ** vector. If successful, output parameter (*prError) is set to the ** squared distance value and SQLITE_OK is returned. Or, if an error ** occurs, an SQLite error code (SQLITE_NOMEM) is returned. */ static int vec1QuantizeReconstructionError2( Vec1Model *pMod, /* Model to use for quantization */ const float *aIn, /* Input vector */ char **pzEnc, double *prError /* OUT: Round trip error */ ){ const int nElem = (int)pMod->hdr.nElem; float *aRound = 0; u8 *aCode = 0; int nCode = vec1EncodedVectorSize(pMod); char *zEnc = 0; int iEnc = 0; int ii; zEnc = (char*)vec1MallocZero( nElem * 8 + nElem * sizeof_f32 + /* aRound[] */ nCode /* aCode[] */ ); if( zEnc==0 ){ return SQLITE_NOMEM; } aRound = (float*)&zEnc[nElem * 8]; aCode = (u8*)&aRound[nElem]; vec1EncodeVector(pMod, aIn, aCode); vec1DecodeVector(pMod, aCode, aRound); *prError = vec1L2Dist(aIn, aRound, nElem); zEnc[iEnc++] = '['; for(ii=0; ii=100 ) zEnc[iEnc++] = (aCode[ii] / 100) + '0'; if( aCode[ii]>=10 ) zEnc[iEnc++] = ((aCode[ii] / 10) % 10) + '0'; zEnc[iEnc++] = (aCode[ii] % 10) + '0'; } zEnc[iEnc++] = ']'; *pzEnc = zEnc; return SQLITE_OK; } static void vec1DistanceStats(sqlite3_context *ctx, Vec1Csr *pCsr){ int rc = SQLITE_OK; if( pCsr->zDistance==0 ){ const float *aVec = 0; int nVec = 0; u8 *pFree = 0; rc = vec1GetVector(pCsr, (const u8**)&aVec, &nVec, &pFree); if( rc==SQLITE_OK && aVec ){ Vec1Tab *pTab = (Vec1Tab*)(pCsr->base.pVtab); int nElem = pTab->cfg.nElem; Vec1Model *pMod = &pTab->mod; int iBucket = 0; double fCoarseError = 0.0; double fReconError = 0.0; double fNorm = 0.0; const float *aTransform = 0; char *zEnc = 0; if( pMod->hdr.nBucket>1 || pMod->hdr.nCodebook>0 ){ aTransform = vec1TransformInputVector(pMod, pTab->aTmpVec, aVec); fNorm = vec1VectorNorm2(aTransform, nElem); } /* Find bucket and coarse error if applicable. */ if( pMod->hdr.nBucket>1 ){ iBucket = vec1BestMatchSimple( pMod->aCentroid, pMod->hdr.nBucket, aVec, nElem, &fCoarseError ); if( aTransform!=pTab->aTmpVec ){ memcpy(pTab->aTmpVec, aTransform, nElem*sizeof_f32); aTransform = pTab->aTmpVec; } if( pTab->mod.hdr.flags & VEC1_MODEL_RESIDUAL ){ vec1SubInPlace(pTab->aTmpVec, &pMod->aCentroid[iBucket*nElem], nElem); } } /* Find reconstruction error if applicable. */ if( pMod->hdr.nCodebook>0 ){ rc = vec1QuantizeReconstructionError2( pMod, aTransform, &zEnc, &fReconError ); } pCsr->zDistance = vec1MPrintf(&rc, "{bucket:%d, coarse_error:%f, reconstruction_error:%f, code: %s}", iBucket, fNorm==0.0 ? 0.0 : sqrt(fCoarseError/fNorm), fNorm==0.0 ? 0.0 : sqrt(fReconError/fNorm), zEnc ? zEnc : "null" ); sqlite3_free(zEnc); } sqlite3_free(pFree); } if( rc!=SQLITE_OK ){ sqlite3_result_error_code(ctx, rc); }else{ sqlite3_result_text(ctx, pCsr->zDistance, -1, SQLITE_TRANSIENT); } } static int vec1ColumnMethod( sqlite3_vtab_cursor *cur, sqlite3_context *ctx, int iCol ){ Vec1Csr *pCsr = (Vec1Csr *)cur; int rc = SQLITE_OK; if( iCol==VEC1_COLUMN_DISTANCE ){ if( pCsr->pQuery ){ double fDist = pCsr->pQuery->heap.aRes[pCsr->iCurrentRes].fDist; sqlite3_result_double(ctx, fDist); }else if( pCsr->bSqlNNQuery ){ int ii = sqlite3_column_count(pCsr->pStmt)-1; sqlite3_result_value(ctx, sqlite3_column_value(pCsr->pStmt, ii)); }else{ vec1DistanceStats(ctx, pCsr); } }else if( iCol==VEC1_COLUMN_VECTOR ){ const u8 *aVec = 0; int nVec = 0; u8 *pFree = 0; rc = vec1GetVector(pCsr, &aVec, &nVec, &pFree); if( rc==SQLITE_OK ){ sqlite3_result_blob(ctx, aVec, nVec, SQLITE_TRANSIENT); }else{ sqlite3_result_error_code(ctx, rc); } sqlite3_free(pFree); }else if( iCol>VEC1_COLUMN_VECTOR ){ rc = vec1SeekCsr(pCsr); if( rc==SQLITE_OK ){ sqlite3_result_value(ctx, sqlite3_column_value(pCsr->pStmt, iCol-2)); } } return rc; } static int vec1Rowid( sqlite3_vtab_cursor *cur, sqlite3_int64 *pRowid ){ Vec1Csr *pCsr = (Vec1Csr *)cur; if( pCsr->pQuery ){ *pRowid = pCsr->pQuery->heap.aRes[pCsr->iCurrentRes].iRowid; }else{ *pRowid = sqlite3_column_int64(pCsr->pStmt, 0); } return SQLITE_OK; } /* ** Return true if constraint iCons of pInfo uses the "binary" collation ** sequence. Or false it it uses anything else. */ static int vec1IsBinary(sqlite3_index_info *pInfo, int iCons){ return 0==sqlite3_stricmp("binary", sqlite3_vtab_collation(pInfo, iCons)); } /* ** xBestIndex implementation. Three plans are supported: ** ** + Full scan. (idxNum==0 or idxNum=1) ** + Lookup by rowid . (idxNum==2 or idxNum=3) ** + KANN search. (idxNum==5) ** ** For KANN search, meta-data constraints are supported. They are encoded ** in idxStr. Specifically, for each handled constraint, there is one ** character to identify the operation, usually followed by one or more ** characters to identify the column the constraint applies to. ** ** idxStr format: */ static int vec1BestIndexMethod( sqlite3_vtab *pVtab, sqlite3_index_info *pIdxInfo ){ struct Vec1IdxOp { int idxop; char vec1op; } aOp[] = { { SQLITE_INDEX_CONSTRAINT_EQ, VEC1_OP_EQ }, { SQLITE_INDEX_CONSTRAINT_LT, VEC1_OP_LT }, { SQLITE_INDEX_CONSTRAINT_GT, VEC1_OP_GT }, { SQLITE_INDEX_CONSTRAINT_LE, VEC1_OP_LE }, { SQLITE_INDEX_CONSTRAINT_GE, VEC1_OP_GE }, { SQLITE_INDEX_CONSTRAINT_IS, VEC1_OP_IS }, { SQLITE_INDEX_CONSTRAINT_ISNULL, VEC1_OP_ISNULL }, { SQLITE_INDEX_CONSTRAINT_ISNOTNULL, VEC1_OP_NOTNULL }, }; const int bDistance = ( pIdxInfo->colUsed & ((1<nConstraint; ii++){ struct sqlite3_index_constraint *p = &pIdxInfo->aConstraint[ii]; if( p->op==SQLITE_INDEX_CONSTRAINT_EQ ){ if( p->usable ){ if( p->iColumn==VEC1_COLUMN_CMD ) iCmd = ii; else if( p->iColumn==VEC1_COLUMN_ARG ) iArg = ii; else if( p->iColumn<0 ) iRowid = ii; }else{ if( p->iColumn==VEC1_COLUMN_CMD || p->iColumn==VEC1_COLUMN_ARG ){ return SQLITE_CONSTRAINT; } } } } if( iCmd>=0 ){ int iLimit = -1; char *idxStr = vec1MallocZero((pIdxInfo->nConstraint*8)+1); int iStr = 0; int argvIndex = 0; if( idxStr==0 ) return SQLITE_NOMEM; pIdxInfo->idxNum = 5; pIdxInfo->needToFreeIdxStr = 1; pIdxInfo->idxStr = idxStr; pIdxInfo->aConstraintUsage[iCmd].argvIndex = (++argvIndex); pIdxInfo->aConstraintUsage[iCmd].omit = 1; if( iArg>=0 ){ pIdxInfo->aConstraintUsage[iArg].argvIndex = (++argvIndex); pIdxInfo->aConstraintUsage[iArg].omit = 1; idxStr[iStr++] = VEC1_OP_PARAMS; } /* Meta-data constraints */ for(ii=0; iinConstraint; ii++){ struct sqlite3_index_constraint *p = &pIdxInfo->aConstraint[ii]; if( p->usable==0 ) continue; if( p->iColumn>VEC1_COLUMN_VECTOR ){ int jj; if( 0==vec1IsBinary(pIdxInfo, ii) ) continue; for(jj=0; jjop==aOp[jj].idxop ){ int iMeta = (p->iColumn - 1 - VEC1_COLUMN_VECTOR); if( jj==0 && sqlite3_vtab_in(pIdxInfo, ii, -1) ){ idxStr[iStr++] = VEC1_OP_IN; sqlite3_vtab_in(pIdxInfo, ii, 1); }else{ idxStr[iStr++] = aOp[jj].vec1op; } idxStr[iStr++] = aHexDigit[iMeta>>4]; idxStr[iStr++] = aHexDigit[iMeta&0xF]; pIdxInfo->aConstraintUsage[ii].argvIndex = (++argvIndex); pIdxInfo->aConstraintUsage[ii].omit = 1; break; } } } if( p->op==SQLITE_INDEX_CONSTRAINT_LIMIT ){ iLimit = ii; } } if( iLimit>=0 && pIdxInfo->nOrderBy==0 && argvIndex==(pIdxInfo->nConstraint-1) ){ idxStr[iStr++] = VEC1_OP_LIMIT; pIdxInfo->aConstraintUsage[iLimit].argvIndex = (++argvIndex); pIdxInfo->aConstraintUsage[iLimit].omit = 1; } }else if( iRowid>=0 ){ pIdxInfo->idxNum = (bDistance ? 3 : 2); pIdxInfo->aConstraintUsage[iRowid].argvIndex = 1; pIdxInfo->aConstraintUsage[iRowid].omit = 1; pIdxInfo->idxFlags |= SQLITE_INDEX_SCAN_UNIQUE; pIdxInfo->estimatedCost = 10.0; pIdxInfo->estimatedRows = 1; }else if( bDistance ){ pIdxInfo->idxNum = 1; } return SQLITE_OK; } static void vec1ListBuilderInitMetaArray(Vec1ListBuilder *p){ const u32 flags = VEC1_META_1BYTEINT|VEC1_META_4BYTEINT|VEC1_META_REAL; int ii; for(ii=0; iipTab->nMeta; ii++){ p->aMeta[ii].flags = flags; p->aMeta[ii].format = VEC1_META_GENERIC; p->aMeta[ii].buf.n = VEC1_META_SZHDR; } } static int vec1ListBuilderInit(Vec1ListBuilder *p, Vec1Tab *pTab, int iBucket){ int rc = SQLITE_OK; p->pTab = pTab; p->iBucket = iBucket; p->szRowid = 4; p->bSorted = 1; if( pTab->mod.hdr.nCodebook ){ if( pTab->mod.hdr.flags & VEC1_MODEL_BITQUANT ){ p->eBlocked = VEC1_BLOCKED_BIN; }else{ p->eBlocked = VEC1_BLOCKED_PQ; } p->nVectorSize = vec1EncodedVectorSize(&pTab->mod); }else{ p->nVectorSize = pTab->cfg.nElem * sizeof_f32; p->eBlocked = VEC1_BLOCKED_NONE; } if( pTab->nMeta>0 ){ int nMeta = pTab->nMeta; p->aMeta = (Vec1MetaBuilder*)vec1MallocZero(sizeof(Vec1MetaBuilder)*nMeta); if( p->aMeta==0 ){ rc = SQLITE_NOMEM; }else{ vec1ListBuilderInitMetaArray(p); } } return rc; } static int vec1WriteMetaBlob( Vec1Tab *pTab, /* Vec1 virtual table */ Vec1Buffer *pBuf, /* Buffer to write from */ i64 iId, /* Id of %_idx row */ int iMeta /* Index of meta-column */ ){ sqlite3_stmt *pStmt = 0; int rc = vec1GetSql(pTab, VEC1_SQL_WRITE_META, &pStmt); assert( iMeta>=0 && iMetaa, pBuf->n, SQLITE_STATIC); sqlite3_step(pStmt); vec1StmtReset(&rc, pStmt); sqlite3_bind_null(pStmt, 2); } return rc; } static int vec1WriteMeta( Vec1Tab *pTab, /* Vec1 virtual table */ Vec1MetaBuilder *p, /* Buffer to write from */ int nEntry, i64 iId, /* Id of %_idx row */ int iMeta /* Index of meta-column */ ){ u32 f = 0; if( (p->format & VEC1_META_GENERIC) && (p->flags & (VEC1_META_1BYTEINT|VEC1_META_4BYTEINT|VEC1_META_REAL)) ){ int iIn = VEC1_META_SZHDR; int iOut = VEC1_META_SZHDR; Vec1MetaValue val = {0,0,0,0}; Vec1Buffer buf = {0, 0, 0}; int rc = SQLITE_OK; int szElem; i64 iNull; if( p->flags & VEC1_META_1BYTEINT ){ szElem = 1; iNull = VEC1_META_1BYTENULL; p->format = VEC1_META_1BYTEINT; }else if( p->flags & VEC1_META_4BYTEINT ){ szElem = sizeof_u32; iNull = VEC1_META_4BYTENULL; p->format = VEC1_META_4BYTEINT; }else{ szElem = sizeof_f64; iNull = VEC1_META_REALNULL; p->format = VEC1_META_REAL; } rc = vec1BufferSize(&buf, VEC1_META_SZHDR + nEntry * szElem); if( rc!=SQLITE_OK ) return rc; while( iInbuf.n ){ rc = vec1MetaValueRead(&p->buf, &iIn, &val); if( rc!=SQLITE_OK ) break; if( val.eType==SQLITE_NULL ){ val.iVal = iNull; }else if( p->format==VEC1_META_REAL ){ if( val.eType==SQLITE_INTEGER ){ val.fVal = (double)val.iVal; } memcpy(&val.iVal, &val.fVal, sizeof_f64); } if( szElem==1 ){ buf.a[iOut] = (val.iVal & 0xFF); }else if( szElem==sizeof_u32 ){ vec1PutU32(&buf.a[iOut], (u32)val.iVal); }else{ vec1PutU64(&buf.a[iOut], (u64)val.iVal); } iOut += szElem; } buf.n = iOut; SWAP(Vec1Buffer, p->buf, buf); vec1BufferFree(&buf); } f = (p->format | (p->flags & VEC1_META_HASNULL)); vec1PutU32(&p->buf.a[0], f); vec1PutU32(&p->buf.a[4], nEntry); return vec1WriteMetaBlob(pTab, &p->buf, iId, iMeta); } /* ** A serialized meta-value is stored in buffer aBuf. Return the size in ** bytes of the meta value. */ static i64 vec1MetaValueSize(const u8 *aBuf){ switch( aBuf[0] ){ case 0: return 1; /* NULL */ case 1: return 2; /* 1-byte integer */ case 2: return 5; /* 4-byte float */ case 3: return 9; /* 8-byte integer */ case 4: return 9; /* 8-byte real */ default: { /* Either a blob or text field. If the size of the value in bytes ** is N, then the varint value V is: ** ** text (odd V values) : V = (2N + 5) -> N = (V - 5) / 2 ** blob (even V values): V = (2N + 6) -> N = (V - 6) / 2 */ i64 V = 0; int n = vec1GetVarint(aBuf, (u64*)&V); return (((V-5)/2) & (i64)0xFFFFFFFF) + n; } } } static int vec1ListBuilderLoad( Vec1ListBuilder *p, i64 iId, i64 iFirst, i64 iLast, const u8 *aVal, int nVal ){ Vec1Tab *pTab = p->pTab; int nEntry = 0; int flags = 0; int rc = SQLITE_OK; int nRowid = 0; int nData = 0; int ii; /* Iterator variable */ assert( p->bufRowid.n==0 ); assert( p->szRowid==4 ); if( vec1CheckIdxSize(pTab, aVal, nVal) ){ return VEC1_CORRUPT; } flags = vec1GetU32(&aVal[0]); nEntry = vec1GetU32(&aVal[4]); p->nTombstone = vec1GetU32(&aVal[8]); if( flags & VEC1_LIST_64BIT ) p->szRowid = 8; /* Set Vec1ListBuilder.eBlocked */ if( pTab->mod.hdr.flags & VEC1_MODEL_BITQUANT ){ p->eBlocked = VEC1_BLOCKED_BIN; }else if( pTab->mod.hdr.nCodebook>0 ){ p->eBlocked = VEC1_BLOCKED_PQ; }else{ p->eBlocked = VEC1_BLOCKED_NONE; } p->bSorted = ((flags & VEC1_LIST_SORTED)!=0); p->iFirst = iFirst; p->iLast = iLast; p->iId = iId; nRowid = nEntry * p->szRowid; nData = nVal - VEC1_LIST_SZHDR - nRowid; rc = vec1BufferGrow(&p->bufRowid, nRowid); if( rc==SQLITE_OK ){ memcpy(p->bufRowid.a, &aVal[VEC1_LIST_SZHDR], nRowid); p->bufRowid.n = nRowid; rc = vec1BufferGrow(&p->bufData, nData); } if( rc==SQLITE_OK ){ memcpy(p->bufData.a, &aVal[VEC1_LIST_SZHDR+nRowid], nData); p->bufData.n = nData; } /* Load the array for each meta-data column from the %_meta table */ for(ii=0; rc==SQLITE_OK && iinMeta; ii++){ Vec1MetaBuilder *pMeta = &p->aMeta[ii]; rc = vec1ReadMeta(pTab, nEntry, &pMeta->buf, iId, ii); if( rc==SQLITE_OK ){ u32 f = vec1GetU32(pMeta->buf.a); pMeta->format = (f & VEC1_META_TYPEMASK); pMeta->flags = f; } } return rc; } static int vec1ListBuilderFlush(Vec1ListBuilder *p){ int rc = SQLITE_OK; if( p->bufRowid.n>0 ){ int nEntry = p->bufRowid.n / p->szRowid; i64 iId = 0; int ii; sqlite3_stmt *pStmt = 0; u8 *aBlob = 0; int nBlob = 0; nBlob = VEC1_LIST_SZHDR + p->bufRowid.n + p->bufData.n; aBlob = (u8*)sqlite3_malloc(nBlob); if( aBlob==0 ){ rc = SQLITE_NOMEM; }else{ int flags = 0; if( p->bSorted ) flags |= VEC1_LIST_SORTED; if( p->szRowid==8 ) flags |= VEC1_LIST_64BIT; vec1PutU32(&aBlob[0], flags); vec1PutU32(&aBlob[4], nEntry); vec1PutU32(&aBlob[8], p->nTombstone); memcpy(&aBlob[VEC1_LIST_SZHDR], p->bufRowid.a, p->bufRowid.n); memcpy(&aBlob[VEC1_LIST_SZHDR+p->bufRowid.n], p->bufData.a, p->bufData.n); if( p->iId>0 ){ /* If there is an existing entry, remove it */ rc = vec1GetSql(p->pTab, VEC1_SQL_DELETE_FROM_IDX, &pStmt); if( rc==SQLITE_OK ){ sqlite3_bind_int64(pStmt, 1, p->iId); sqlite3_step(pStmt); vec1StmtReset(&rc, pStmt); } } if( rc==SQLITE_OK ){ rc = vec1GetSql(p->pTab, VEC1_SQL_INSERT_IDX, &pStmt); } if( rc==SQLITE_OK ){ if( p->iId==0 ){ sqlite3_bind_null(pStmt, 1); }else{ sqlite3_bind_int64(pStmt, 1, p->iId); } sqlite3_bind_int(pStmt, 2, p->iBucket); sqlite3_bind_int64(pStmt, 3, p->iFirst); sqlite3_bind_int64(pStmt, 4, p->iLast); rc = sqlite3_bind_blob(pStmt, 5, aBlob, nBlob, SQLITE_STATIC); sqlite3_step(pStmt); vec1StmtReset(&rc, pStmt); if( rc==SQLITE_OK ){ iId = sqlite3_last_insert_rowid(p->pTab->db); } } sqlite3_free(aBlob); /* If an error occurred within the sqlite3_step() call above, then the ** transaction may have been rolled back and (*p) freed. So take ** care not to access it again if rc!=SQLITE_OK. */ if( rc==SQLITE_OK ){ p->bufRowid.n = 0; p->bufData.n = 0; p->iId = 0; p->iId = 0; } } for(ii=0; rc==SQLITE_OK && iipTab->nMeta; ii++){ rc = vec1WriteMeta(p->pTab, &p->aMeta[ii], nEntry, iId, ii); if( rc==SQLITE_OK ) p->aMeta[ii].buf.n = VEC1_META_SZHDR; } /* Same reasoning as above - if rc!=SQLITE_OK, then the transaction ** may have been rolled back and (*p) freed already. */ if( rc==SQLITE_OK ){ p->bSorted = 1; } }else if( p->iId>0 ){ sqlite3_stmt *pDelete = 0; rc = vec1GetSql(p->pTab, VEC1_SQL_DELETE_FROM_IDX, &pDelete); if( rc==SQLITE_OK ){ sqlite3_bind_int64(pDelete, 1, p->iId); sqlite3_step(pDelete); vec1StmtReset(&rc, pDelete); } if( p->pTab->nMeta>0 && rc==SQLITE_OK ){ rc = vec1GetSql(p->pTab, VEC1_SQL_DELETE_FROM_META, &pDelete); if( rc==SQLITE_OK ){ sqlite3_bind_int64(pDelete, 1, p->iId); sqlite3_step(pDelete); vec1StmtReset(&rc, pDelete); } } } return rc; } /* ** Return the offset of the first field of vector ii within a blocked ** storage buffer. Each vector is nVectorSize bytes in size (including ** the norm for binary-quantized vectors). szElem0 is the size of the ** first element in each vector (4 for binary-quant, 1 for product-quant). */ #define VEC1_LIST_BLOCKED_OFFSET(ii, nVectorSize) ( \ ((ii / VEC1_PQ_BLOCKSIZE) * VEC1_PQ_BLOCKSIZE * nVectorSize) + \ (ii % VEC1_PQ_BLOCKSIZE) \ ) /* ** Return the offset of the norm field for vector ii within a blocked ** buffer containing binary quanitized vectors. Each quantized vector ** is nCodebook bytes in size, not including the 4 byte norm. */ #define VEC1_LIST_NORM_OFFSET(ii, nCodebook) ( \ ((ii / VEC1_PQ_BLOCKSIZE) * VEC1_PQ_BLOCKSIZE * (nCodebook+sizeof_f32)) + \ (ii % VEC1_PQ_BLOCKSIZE) * sizeof_f32 \ ) #define VEC1_LIST_CODE_OFFSET(ii, nCodebook) ( \ ((ii / VEC1_PQ_BLOCKSIZE) * VEC1_PQ_BLOCKSIZE * (nCodebook+sizeof_f32)) + \ (VEC1_PQ_BLOCKSIZE * sizeof_f32) + \ (ii % VEC1_PQ_BLOCKSIZE) \ ) /* ** Shuffle the contents of the list-builder around so that it ** contains 0 tombstones. */ static int vec1ListBuilderCompress( Vec1ListBuilder *p, i64 iDel ){ const int nMeta = p->pTab->nMeta; int ii; int iOut = 0; int nEntry = p->bufRowid.n / p->szRowid; int *aMetaIn = 0; /* Meta-data input offsets */ int *aMetaOut = 0; /* Meta-data output offsets */ i64 tomb1 = p->szRowid==8 ? VEC1_TOMBSTONE_64 : VEC1_TOMBSTONE_32; i64 tomb2 = iDel; /* Allocate space for aMetaIn[] and aMetaOut[] if requried */ if( nMeta>0 ){ aMetaIn = vec1MallocZero(nMeta * 2 * sizeof(int)); if( aMetaIn==0 ) return SQLITE_NOMEM; aMetaOut = &aMetaIn[nMeta]; for(ii=0; iiiFirst = VEC1_LARGEST_INT64; p->iLast = VEC1_SMALLEST_INT64; for(ii=0; iiszRowid*ii; i64 iRowid; int bTombstone = 0; int iMeta; /* Iterator used for meta-data columns */ if( p->szRowid==8 ){ iRowid = vec1GetU64(&p->bufRowid.a[iOff]); }else{ iRowid = vec1GetU32(&p->bufRowid.a[iOff]); } if( iRowid==tomb1 || iRowid==tomb2 ) bTombstone = 1; if( bTombstone==0 ){ p->iFirst = MIN(p->iFirst, iRowid); p->iLast = MAX(p->iLast, iRowid); if( ii!=iOut ){ int iOff2 = p->szRowid*iOut; u8 *a = p->bufData.a; int iFrom, iTo; memcpy(&p->bufRowid.a[iOff2], &p->bufRowid.a[iOff], p->szRowid); if( p->eBlocked==VEC1_BLOCKED_NONE ){ int i1 = p->nVectorSize * iOut; int i2 = p->nVectorSize * ii; memcpy(&p->bufData.a[i1], &p->bufData.a[i2], p->nVectorSize); }else{ int nCodebook; /* Size of quantized vector without norm */ int jj; if( p->eBlocked==VEC1_BLOCKED_BIN ){ nCodebook = p->nVectorSize - sizeof_f32; memcpy(&a[ VEC1_LIST_NORM_OFFSET(iOut, nCodebook) ], &a[ VEC1_LIST_NORM_OFFSET(ii, nCodebook) ], sizeof_f32 ); iFrom = VEC1_LIST_CODE_OFFSET(ii, nCodebook); iTo = VEC1_LIST_CODE_OFFSET(iOut, nCodebook); }else{ assert( p->eBlocked==VEC1_BLOCKED_PQ ); nCodebook = p->nVectorSize; iFrom = VEC1_LIST_BLOCKED_OFFSET(ii, nCodebook); iTo = VEC1_LIST_BLOCKED_OFFSET(iOut, nCodebook); } for(jj=0; jjbufData.a[iFrom + jj*VEC1_PQ_BLOCKSIZE]; p->bufData.a[iTo + jj*VEC1_PQ_BLOCKSIZE] = v; } } } iOut++; } for(iMeta=0; iMetaaMeta[iMeta]; u8 *a = pMeta->buf.a; if( pMeta->format==VEC1_META_GENERIC ){ i64 nIn = vec1MetaValueSize(&a[aMetaIn[iMeta]]); if( nIn<=0 || nIn>pMeta->buf.n-aMetaIn[iMeta] ){ sqlite3_free(aMetaIn); return VEC1_CORRUPT; } if( bTombstone==0 ){ if( aMetaIn[iMeta]!=aMetaOut[iMeta] ){ memmove(&a[ aMetaOut[iMeta] ], &a[ aMetaIn[iMeta] ], nIn); } aMetaOut[iMeta] += nIn; } aMetaIn[iMeta] += nIn; }else{ int szElem = (pMeta->format==VEC1_META_1BYTEINT) ? 1 : (pMeta->format==VEC1_META_4BYTEINT) ? sizeof_u32 : sizeof_f64 ; if( bTombstone==0 ){ memmove(&a[ aMetaOut[iMeta] ], &a[ aMetaIn[iMeta] ], szElem); aMetaOut[iMeta] += szElem; } aMetaIn[iMeta] += szElem; } } } p->nTombstone = 0; p->bufRowid.n = iOut*p->szRowid; if( p->eBlocked==VEC1_BLOCKED_NONE ){ p->bufData.n = p->nVectorSize * iOut; }else{ p->bufData.n = p->nVectorSize * VEC1_PQ_BLOCKSIZE * ( ((iOut+VEC1_PQ_BLOCKSIZE-1)/VEC1_PQ_BLOCKSIZE) ); } for(ii=0; iiaMeta[ii].buf.n = aMetaOut[ii]; } sqlite3_free(aMetaIn); return SQLITE_OK; } /* ** Append a rowid/[PQ|vector] pair to a list builder object. */ static int vec1ListBuilderAdd( Vec1ListBuilder *p, i64 iRowid, /* Rowid value to add to builder */ const u8 *aPQ /* Pointer to buffer containing PQ enc. */ ){ int rc = SQLITE_OK; int nFinal = 0; const int vs = p->nVectorSize; assert( p->szRowid==4 || p->szRowid==8 ); /* Check if adding this entry would cause the final blob to be larger ** than the configured 'blocksize'. If so, flush it to disk. ** ** If the proposed rowid may not be added to the current list because ** it is too large or a tombstone value, flush the current list to disk ** in that case as well. */ nFinal = p->bufRowid.n + p->bufData.n + p->szRowid + p->nVectorSize; if( nFinal>p->pTab->cfg.nBlocksize || (p->szRowid==4 && (iRowid<0 || iRowid>=VEC1_TOMBSTONE_32)) || (p->szRowid==8 && iRowid==VEC1_TOMBSTONE_64) ){ rc = vec1ListBuilderFlush(p); if( rc!=SQLITE_OK ) return rc; p->szRowid = ((iRowid<0 || iRowid>=VEC1_TOMBSTONE_32) ? 8 : 4); p->bSorted = 1; p->iId = 0; p->nTombstone = 0; vec1ListBuilderInitMetaArray(p); } /* Update Vec1ListBuilder.iFirst, iLast and bSorted to account for the ** rowid value being added by this call. */ if( p->bufRowid.n==0 ){ p->iLast = p->iFirst = iRowid; }else if( iRowidiLast ){ p->bSorted = 0; p->iFirst = MIN(p->iFirst, iRowid); }else{ p->iLast = iRowid; } /* Ensure that there is room in the two buffers for this entry. The ** rowids buffer always grows by szRowid bytes. How much the data ** buffer grows by depends on whether it is blocked or not. */ rc = vec1BufferGrow(&p->bufRowid, p->szRowid); if( rc==SQLITE_OK ){ if( p->eBlocked==VEC1_BLOCKED_NONE ){ rc = vec1BufferGrow(&p->bufData, p->nVectorSize); }else{ int nEntry = (p->bufRowid.n / p->szRowid); if( (nEntry % VEC1_PQ_BLOCKSIZE)==0 ){ int nReq = (VEC1_PQ_BLOCKSIZE * p->nVectorSize); rc = vec1BufferGrow(&p->bufData, nReq); if( rc==SQLITE_OK ){ memset(&p->bufData.a[p->bufData.n], 0, nReq); p->bufData.n += nReq; } } } } if( rc==SQLITE_OK ){ /* Write the vector or PQ data */ if( p->eBlocked==VEC1_BLOCKED_NONE ){ memcpy(&p->bufData.a[p->bufData.n], aPQ, p->nVectorSize); p->bufData.n += p->nVectorSize; }else{ const int iEntry = (p->bufRowid.n / p->szRowid); const int iBlkOff = (iEntry / VEC1_PQ_BLOCKSIZE) * vs * VEC1_PQ_BLOCKSIZE; const int iBlkEntry = iEntry % VEC1_PQ_BLOCKSIZE; int ii = 0; int iOff = iBlkOff + iBlkEntry; if( p->eBlocked==VEC1_BLOCKED_BIN ){ /* In this case, the first 4 bytes of the encoded vector is its norm. ** Each block begins with an array of VEC1_PQ_BLOCKSIZE norm values. ** Copy the norm for this vector in now. */ const int iNorm = iBlkOff + (sizeof_f32 * iBlkEntry); memcpy(&p->bufData.a[iNorm], aPQ, sizeof_f32); ii = 4; iOff += (sizeof_f32 * VEC1_PQ_BLOCKSIZE) - ii*VEC1_PQ_BLOCKSIZE; } /* Write each byte of the PQ or binary quant code */ for(/* no-op */; iinVectorSize; ii++){ p->bufData.a[iOff + ii*VEC1_PQ_BLOCKSIZE] = aPQ[ii]; } } /* Write the rowid */ if( p->szRowid==8 ){ vec1PutU64(&p->bufRowid.a[p->bufRowid.n], iRowid); }else{ vec1PutU32(&p->bufRowid.a[p->bufRowid.n], (u32)iRowid); } p->bufRowid.n += p->szRowid; } return rc; } static void vec1ListBuilderFree(Vec1ListBuilder *p){ int ii; vec1BufferFree(&p->bufRowid); vec1BufferFree(&p->bufData); if( p->aMeta ){ for(ii=0; iipTab->nMeta; ii++){ vec1BufferFree(&p->aMeta[ii].buf); } } sqlite3_free(p->aMeta); } /* ** Allocate a new writer object for vec1 table pTab. */ static int vec1WriterAlloc( Vec1Tab *pTab, /* Vec1 table */ int bRebuild, /* True if this is 'rebuild' op */ Vec1Writer **pp /* OUT: New writer object */ ){ int rc = SQLITE_OK; Vec1Writer *p = 0; int nBld = pTab->mod.hdr.nBucket ? pTab->mod.hdr.nBucket : 1; int nEncoded = vec1EncodedVectorSize(&pTab->mod); int nByte = sizeof(Vec1Writer) + /* Vec1Writer */ sizeof(Vec1ListBuilder) * nBld + /* Vec1Writer.aBld */ sizeof_f32 * pTab->nTmpVec + /* Vec1Writer.aResidual */ nEncoded; /* Vec1Writer.aPQ */ p = (Vec1Writer*)vec1MallocZero(nByte); if( p==0 ){ rc = SQLITE_NOMEM; }else{ memset(p, 0, nByte); p->aBld = (Vec1ListBuilder*)&p[1]; p->aResidual = (float*)&p->aBld[nBld]; p->aPQ = (u8*)&p->aResidual[pTab->nTmpVec]; p->pTab = pTab; p->bRebuild = bRebuild; p->nBld = nBld; if( bRebuild ){ int ii; for(ii=0; rc==SQLITE_OK && iiaBld[ii], pTab, ii); } } } *pp = p; return rc; } /* ** Quantize a vector so that it can be written to the index. */ static void vec1QuantizeVector( const Vec1Model *pMod, /* Current model */ float *aTmp, /* Temporary space - same dim as vectors */ const float *aVector, /* Vector to quantize */ int *piBucket, /* OUT: Bucket to put vector in */ u8 *aCode /* OUT: Write PQ code (if any) here */ ){ const int nBucket = pMod->hdr.nBucket; const float *aVec; int iBucket = 0; aVec = vec1TransformInputVector(pMod, aTmp, aVector); if( nBucket>0 ){ int nElem = pMod->hdr.nElem; iBucket = vec1BestMatchSimple(pMod->aCentroid, nBucket, aVec, nElem, 0); if( pMod->hdr.nCodebook>0 && (pMod->hdr.flags & VEC1_MODEL_RESIDUAL) ){ vec1Sub(aTmp, aVec, &pMod->aCentroid[iBucket*nElem], nElem); aVec = aTmp; } } if( pMod->hdr.nCodebook>0 ){ vec1EncodeVector(pMod, aVec, aCode); } *piBucket = iBucket; } static int vec1WriterVector( Vec1Writer *p, /* Writer object to write to */ i64 iRowid, /* Rowid of new entry */ const float *aVector, /* New vector */ int *piBucket ){ Vec1Tab *pTab = p->pTab; const u8 *aStore; Vec1ListBuilder *pBld = 0; const Vec1Model *pMod = &pTab->mod; int rc = SQLITE_OK; int iBld = *piBucket; /* Transform and quantize the vector */ if( iBld<0 ){ vec1QuantizeVector(pMod, p->aResidual, aVector, &iBld, p->aPQ); } if( pMod->hdr.nCodebook>0 ){ aStore = p->aPQ; }else{ /* If the index is storing full vectors, not PQ codes, store the original, ** not the transformed vector. */ aStore = (const u8*)aVector; } pBld = &p->aBld[iBld]; if( pBld->pTab==0 ){ /* Attempt to load blob smaller than 'blocksize-min' belonging to ** this bucket to append to. */ sqlite3_stmt *pSearch = 0; rc = vec1ListBuilderInit(pBld, pTab, iBld); if( rc==SQLITE_OK ){ rc = vec1GetSql(pTab, VEC1_SQL_SEARCH_IDX_FOR_INSERT, &pSearch); } if( rc==SQLITE_OK ){ sqlite3_bind_int(pSearch, 1, iBld); sqlite3_bind_int(pSearch, 2, pTab->cfg.nBlocksizeMin); if( SQLITE_ROW==sqlite3_step(pSearch) ){ i64 iId = sqlite3_column_int64(pSearch, 0); i64 iFirst = sqlite3_column_int64(pSearch, 1); i64 iLast = sqlite3_column_int64(pSearch, 2); const u8 *aVal = (const u8*)sqlite3_column_blob(pSearch, 3); int nVal = sqlite3_column_bytes(pSearch, 3); rc = vec1ListBuilderLoad(pBld, iId, iFirst, iLast, aVal, nVal); } vec1StmtReset(&rc, pSearch); } } if( rc==SQLITE_OK ){ rc = vec1ListBuilderAdd(pBld, iRowid, aStore); } *piBucket = iBld; return rc; } /* ** Serialize value pVal and append it to buffer pBuf. Vec1 uses a serialization ** format based on SQLite's. First a varint indicating the type and size ** of the value, followed by the data for the value itself. Text values ** are always encoded using utf-8. ** ** 0: NULL value. 0 byte payload. ** 1: Integer value (unsigned). 1 byte payload. ** 2: Integer value (signed). 4 byte payload. ** 3: Integer value (signed). 8 byte payload. ** 4: Real value. 8 byte payload. ** ** Greater than 4 and odd is a text value. Payload size ((v-5)/2). Greater ** than 4 and even is a blob value. Payload size ((v-6)/2). */ static int vec1AppendMetaValue( Vec1Buffer *pBuf, sqlite3_value *pVal ){ int rc = SQLITE_OK; switch( sqlite3_value_type(pVal) ){ case SQLITE_NULL: { rc = vec1BufferGrow(pBuf, 1); if( rc==SQLITE_OK ){ pBuf->a[pBuf->n++] = 0x00; } break; } case SQLITE_INTEGER: { i64 iVal = sqlite3_value_int64(pVal); rc = vec1BufferGrow(pBuf, 9); if( rc==SQLITE_OK ){ if( iVal>=0 && iVal<=254 ){ pBuf->a[pBuf->n++] = 0x01; pBuf->a[pBuf->n++] = (iVal & 0xFF); } else if( iVal>=VEC1_META_4BYTEMIN && iVal<=VEC1_META_4BYTEMAX ){ pBuf->a[pBuf->n++] = 0x02; vec1PutU32(&pBuf->a[pBuf->n], (int)iVal); pBuf->n += 4; }else{ pBuf->a[pBuf->n++] = 0x03; vec1PutU64(&pBuf->a[pBuf->n], iVal); pBuf->n += 8; } } break; } case SQLITE_FLOAT: { double fVal = sqlite3_value_double(pVal); rc = vec1BufferGrow(pBuf, 9); if( rc==SQLITE_OK ){ i64 iVal; memcpy(&iVal, &fVal, sizeof(fVal)); pBuf->a[pBuf->n++] = 0x04; vec1PutU64(&pBuf->a[pBuf->n], iVal); pBuf->n += 8; } break; } case SQLITE_TEXT: { const char *z = (const char*)sqlite3_value_text(pVal); int n = sqlite3_value_bytes(pVal); rc = vec1BufferGrow(pBuf, n+5); if( rc==SQLITE_OK ){ pBuf->n += vec1PutVarint(&pBuf->a[pBuf->n], (n*2 + 5)); memcpy(&pBuf->a[pBuf->n], z, n); pBuf->n += n; } break; } default: { const u8 *z = (const u8*)sqlite3_value_blob(pVal); int n = sqlite3_value_bytes(pVal); rc = vec1BufferGrow(pBuf, n+5); if( rc==SQLITE_OK ){ pBuf->n += vec1PutVarint(&pBuf->a[pBuf->n], (n*2 + 6)); memcpy(&pBuf->a[pBuf->n], z, n); pBuf->n += n; } assert( sqlite3_value_type(pVal)==SQLITE_BLOB ); break; } } return rc; } static void vec1MetaValueUpdateFlags(const u8 *aMeta, u32 *pFlags){ switch( aMeta[0] ){ case 0: /* NULL value */ *pFlags |= VEC1_META_HASNULL; break; case 1: /* 1-byte integer */ break; case 2: /* 4-byte integer */ *pFlags &= ~(VEC1_META_1BYTEINT); break; case 4: /* 8-byte real */ *pFlags &= ~(VEC1_META_1BYTEINT|VEC1_META_4BYTEINT); break; default: *pFlags &= ~(VEC1_META_1BYTEINT|VEC1_META_4BYTEINT|VEC1_META_REAL); break; } } /* ** The meta-list builder passed as the only argument currently contains ** a list of values in non-generic format (e.g. VEC1_META_1BYTEINT format). ** This function converts the list to generic format. ** ** Return SQLITE_OK if successful, or an SQLite error code (SQLITE_NOMEM) ** otherwise. */ static int vec1ExpandMetaValue(Vec1MetaBuilder *p){ Vec1Buffer orig; int rc = SQLITE_OK; int bHasNull = 0; memcpy(&orig, &p->buf, sizeof(Vec1Buffer)); p->buf.a = 0; p->buf.n = VEC1_META_SZHDR; p->buf.nAlloc = 0; assert( p->format==VEC1_META_1BYTEINT || p->format==VEC1_META_4BYTEINT || p->format==VEC1_META_REAL ); rc = vec1BufferGrow(&p->buf, (orig.n * 2)); if( rc==SQLITE_OK ){ int iIn = VEC1_META_SZHDR; int iOut = VEC1_META_SZHDR; while( iInformat==VEC1_META_1BYTEINT ){ int v = orig.a[iIn++]; if( v==VEC1_META_1BYTENULL ){ p->buf.a[iOut++] = 0x00; bHasNull = 1; }else{ p->buf.a[iOut++] = 0x01; p->buf.a[iOut++] = (v & 0xFF); } }else if( p->format==VEC1_META_4BYTEINT ){ u32 v = vec1GetU32(&orig.a[iIn]); iIn += sizeof_u32; if( v==VEC1_META_4BYTENULL ){ p->buf.a[iOut++] = 0x00; bHasNull = 1; }else{ p->buf.a[iOut++] = 0x02; vec1PutU32(&p->buf.a[iOut], (u32)v); iOut += sizeof_u32; } }else{ u64 v = vec1GetU64(&orig.a[iIn]); iIn += sizeof_f64; if( v==VEC1_META_REALNULL ){ p->buf.a[iOut++] = 0x00; bHasNull = 1; }else{ p->buf.a[iOut++] = 0x04; vec1PutU64(&p->buf.a[iOut], v); iOut += sizeof_f64; } } } p->buf.n = iOut; } if( p->format==VEC1_META_1BYTEINT ){ p->flags = VEC1_META_4BYTEINT | VEC1_META_1BYTEINT | VEC1_META_REAL; }else if( p->format==VEC1_META_4BYTEINT ){ p->flags = VEC1_META_4BYTEINT | VEC1_META_REAL; }else{ p->flags = VEC1_META_REAL; } p->format = VEC1_META_GENERIC; if( bHasNull ){ p->flags |= VEC1_META_HASNULL; } vec1BufferFree(&orig); return rc; } static int vec1WriterMetaFromArray( Vec1Writer *pWriter, /* Writer object accumulating changes */ int iBucket, /* Bucket to write to */ sqlite3_value **apMeta /* Meta values associated with prev vector */ ){ Vec1ListBuilder *p = &pWriter->aBld[iBucket]; int ii; int rc = SQLITE_OK; assert( p->pTab!=0 ); for(ii=0; rc==SQLITE_OK && iipTab->nMeta; ii++){ Vec1MetaBuilder *pMeta = &p->aMeta[ii]; Vec1Buffer *pBuf = &pMeta->buf; sqlite3_value *pVal = apMeta[ii]; if( pMeta->format==VEC1_META_1BYTEINT || pMeta->format==VEC1_META_4BYTEINT || pMeta->format==VEC1_META_REAL ){ int eType = sqlite3_value_type(pVal); rc = vec1BufferGrow(pBuf, sizeof_f64); if( rc!=SQLITE_OK ) continue; if( eType==SQLITE_INTEGER ){ i64 iVal = sqlite3_value_int64(pVal); if( pMeta->format==VEC1_META_1BYTEINT ){ if( iVal>=0 && iVal<=254 ){ pBuf->a[pBuf->n++] = (iVal & 0xFF); pVal = 0; } }else{ if( iVal>=VEC1_META_4BYTEMIN && iVal<=VEC1_META_4BYTEMAX ){ if( pMeta->format==VEC1_META_4BYTEINT ){ vec1PutU32(&pBuf->a[pBuf->n], (u32)iVal); pBuf->n += sizeof_u32; }else{ double fVal = (double)iVal; u64 v; memcpy(&v, &fVal, sizeof_f64); vec1PutU64(&pBuf->a[pBuf->n], v); pBuf->n += sizeof_f64; } pVal = 0; } } }else if( eType==SQLITE_FLOAT && pMeta->format==VEC1_META_REAL ){ double fVal = sqlite3_value_double(pVal); u64 v; memcpy(&v, &fVal, sizeof_f64); vec1PutU64(&pBuf->a[pBuf->n], v); pBuf->n += sizeof_f64; pVal = 0; }else if( eType==SQLITE_NULL ){ rc = vec1BufferGrow(pBuf, sizeof_f64); if( pMeta->format==VEC1_META_1BYTEINT ){ pBuf->a[pBuf->n++] = (VEC1_META_1BYTENULL & 0xFF); } else if( pMeta->format==VEC1_META_4BYTEINT ){ vec1PutU32(&pBuf->a[pBuf->n], (u32)VEC1_META_4BYTENULL); pBuf->n += sizeof_u32; }else{ vec1PutU64(&pBuf->a[pBuf->n], (u64)VEC1_META_REALNULL); pBuf->n += sizeof_f64; } pVal = 0; } if( pVal ){ rc = vec1ExpandMetaValue(pMeta); } } if( pMeta->format==VEC1_META_GENERIC ){ int iOff = pBuf->n; rc = vec1AppendMetaValue(pBuf, pVal); if( rc==SQLITE_OK ){ vec1MetaValueUpdateFlags(&pBuf->a[iOff], &pMeta->flags); } } } return rc; } static int vec1WriterMetaFromStmt( Vec1Writer *pWriter, /* Writer object accumulating changes */ int iBucket, /* Bucket to write to */ sqlite3_stmt *pStmt ){ Vec1ListBuilder *p = &pWriter->aBld[iBucket]; int ii; int rc = SQLITE_OK; assert( p->pTab!=0 ); for(ii=0; rc==SQLITE_OK && iipTab->nMeta; ii++){ Vec1Buffer *pBuf = &p->aMeta[ii].buf; sqlite3_value *pVal = sqlite3_column_value(pStmt, ii+2); int iOff = pBuf->n; rc = vec1AppendMetaValue(pBuf, pVal); if( rc==SQLITE_OK ){ vec1MetaValueUpdateFlags(&pBuf->a[iOff], &p->aMeta[ii].flags); } } return rc; } /* ** Read nMeta values from buffer pBuf. Use them as the meta values associated ** with the (compressed) vector just appended to bucket iBucket. ** ** This is only called on a buffer that has been constructed in process, ** not one loaded from disk, so it is safe to assume that pBuf really does ** contain nMeta well-formed values. */ static int vec1WriterMetaFromPacked( Vec1Writer *pWriter, /* Writer object accumulating changes */ int iBucket, /* Bucket to write to */ Vec1Buffer *pBuf, int *piOff /* IN/OUT: Buffer offset */ ){ int ii; int iOff = *piOff; for(ii=0; iipTab->nMeta; ii++){ Vec1MetaBuilder *pTo = &pWriter->aBld[iBucket].aMeta[ii]; i64 n = vec1MetaValueSize(&pBuf->a[iOff]); int rc = vec1BufferGrow(&pTo->buf, n); vec1MetaValueUpdateFlags(&pBuf->a[iOff], &pTo->flags); if( rc!=SQLITE_OK ) return rc; memcpy(&pTo->buf.a[pTo->buf.n], &pBuf->a[iOff], n); pTo->buf.n += n; iOff += n; } *piOff = iOff; return SQLITE_OK; } static int vec1PackMetaValues( Vec1Tab *pTab, Vec1Buffer *pBuf, sqlite3_stmt *pStmt ){ int ii; int rc = SQLITE_OK; for(ii=0; rc==SQLITE_OK && iinMeta; ii++){ rc = vec1AppendMetaValue(pBuf, sqlite3_column_value(pStmt, 2+ii)); } return rc; } static int vec1WriterQuantized( Vec1Writer *pWriter, i64 iRowid, int iBucket, const u8 *aStore ){ return vec1ListBuilderAdd(&pWriter->aBld[iBucket], iRowid, aStore); } static int vec1WriterFinish(Vec1Writer *p, int rcin){ int rc = rcin; if( p ){ int ii; for(ii=0; iinBld; ii++){ if( rc==SQLITE_OK ){ rc = vec1ListBuilderFlush(&p->aBld[ii]); } vec1ListBuilderFree(&p->aBld[ii]); } sqlite3_free(p); } return rc; } static void vec1EncodeVectorN( const Vec1Model *pMod, const float *aVec[VEC1_MULTIMATCH_NVEC], u8 *aCode ){ if( pMod->hdr.flags & VEC1_MODEL_BITQUANT ){ int nCode = vec1EncodedVectorSize(pMod); int vv; for(vv=0; vvhdr.nCodebook; const int nCodeElem = pMod->nCodeElem; const float *aSub[VEC1_MULTIMATCH_NVEC]; int vv; int k; for(k=0; kaModel[k * nCodeElem * VEC1_PQ_CODEBOOK_SZ], &pMod->aModelNT[k * nCodeElem * VEC1_PQ_CODEBOOK_SZ], VEC1_PQ_CODEBOOK_SZ, aSub, aBest, 0 ); for(vv=0; vvhdr.nElem; int vv; for(vv=0; vvhdr.flags & VEC1_MODEL_ROTATE) ){ float *aRot[VEC1_MULTIMATCH_NVEC]; for(vv=0; vvaRotation, pMod->aRotationNT ); #else vec1RotateVectorN(aRot, aOut, nElem, pMod->aRotation); #endif memcpy(aOut, aRot, sizeof(aRot)); } else if( (pMod->hdr.flags & VEC1_MODEL_WHT) ){ for(vv=0; vvhdr.eDistance==VEC1_DISTANCE_COS || nStride!=nElem ){ for(vv=0; vvhdr.eDistance==VEC1_DISTANCE_COS ){ for(vv=0; vvhdr.nElem; const int nBucket = pMod->hdr.nBucket; const int nStride = MAX(nElem, pMod->nCodeElem * (int)pMod->hdr.nCodebook); const float *aVec[VEC1_MULTIMATCH_NVEC]; int vv; vec1TransformInputVectorN(pMod, aVector, aVec, aTmp, nStride); if( nBucket>0 ){ vec1BestMatchPlatformN( nElem, pMod->aCentroid, pMod->aCentroidNT, nBucket, aVec, aBucket, 0 ); if( pMod->hdr.nCodebook>0 && (pMod->hdr.flags & VEC1_MODEL_RESIDUAL) ){ for(vv=0; vvaCentroid[aBucket[vv]*nElem], nElem); aVec[vv] = tmp; } } for(vv=0; vv=0 && aBucket[vv]hdr.nCodebook>0 ){ vec1EncodeVectorN(pMod, aVec, aCode); } } /* ** Each Vec1QuantizeJob has capacity for this many vectors. */ #define VEC1_QUANTIZE_JOB_SZ 1024 typedef struct Vec1QuantizeJob Vec1QuantizeJob; struct Vec1QuantizeJob { Vec1Model *pModel; Vec1Writer *pWriter; int nVector; /* Number of vectors to quantize */ i64 *aRowid; /* Array of nVector rowids */ float *aTmp; /* Temp space for one vector */ float *aVec; /* Packed vectors */ int *aBucket; /* OUT: Bucket for each vector */ u8 *aCode; /* OUT: Packed codes for vectors */ int nEncoded; /* Size of each encoded vector in bytes */ Vec1Buffer meta; /* Packed array of meta values */ }; static void vec1QuantizeJob(void *pCtx){ Vec1QuantizeJob *p = (Vec1QuantizeJob*)pCtx; const int nQuant = VEC1_MULTIMATCH_NVEC; const int nElem = p->pModel->hdr.nElem; int ii = 0; /* Handle the bulk of the vectors VEC1_QUANTIZE_JOB_QUANTA at a time. */ for(; ii<=(p->nVector-nQuant); ii += nQuant){ float *v = &p->aVec[nElem * ii]; u8 *a = &p->aCode[p->nEncoded * ii]; vec1QuantizeVectorN(p->pModel, p->aTmp, v, &p->aBucket[ii], a); } /* Scalar tail - handle remaining vectors one at a time. */ for(; iinVector; ii++){ float *v = &p->aVec[nElem * ii]; u8 *a = &p->aCode[p->nEncoded * ii]; vec1QuantizeVector(p->pModel, p->aTmp, v, &p->aBucket[ii], a); } } static int vec1UpdateBase( Vec1Tab *pTab, i64 iId, int iBucket ){ sqlite3_stmt *pStmt = 0; int rc = vec1GetSql(pTab, VEC1_SQL_UPDATE_BASE2, &pStmt); if( rc==SQLITE_OK ){ sqlite3_bind_int(pStmt, 1, iBucket); sqlite3_bind_int64(pStmt, 2, iId); sqlite3_step(pStmt); vec1StmtReset(&rc, pStmt); } return rc; } static int vec1QuantizeJobFinish(void *pCtx, int rcin){ Vec1QuantizeJob *p = (Vec1QuantizeJob*)pCtx; const int nElem = p->pModel->hdr.nElem; int rc = rcin; int ii; int bBucketBase = vec1ModelIsFlat(p->pModel); int iOffMeta = 0; assert( bBucketBase==0 || p->pModel->hdr.nCodebook==0 ); for(ii=0; rc==SQLITE_OK && iinVector; ii++){ int iBucket = p->aBucket[ii]; const u8 *aStore; if( p->nEncoded>0 ){ aStore = &p->aCode[ii * p->nEncoded]; }else{ aStore = (const u8*)&p->aVec[ii * nElem]; } rc = vec1WriterQuantized(p->pWriter, p->aRowid[ii], iBucket, aStore); if( bBucketBase && rc==SQLITE_OK ){ rc = vec1UpdateBase(p->pWriter->pTab, p->aRowid[ii], iBucket); } if( rc==SQLITE_OK ){ rc = vec1WriterMetaFromPacked(p->pWriter, iBucket, &p->meta, &iOffMeta); } } vec1BufferFree(&p->meta); sqlite3_free(p); return rc; } static int vec1UpdateBaseRows( Vec1Tab *pTab, i64 iFirst, i64 iLast ){ sqlite3_stmt *pStmt = 0; int rc = vec1GetSql(pTab, VEC1_SQL_ZERO_BASE_RANGE, &pStmt); if( rc==SQLITE_OK ){ sqlite3_bind_int64(pStmt, 1, iFirst); sqlite3_bind_int64(pStmt, 2, iLast); sqlite3_step(pStmt); vec1StmtReset(&rc, pStmt); } return rc; } #define VEC1_ROWS_PER_BASE_UPDATE 64 /* ** Rebuild the entire ANN index from scratch, based on the current contents ** of the %_base table. */ static int vec1RebuildIndex(Vec1Tab *pTab, int nThread){ const int nElem = pTab->cfg.nElem; sqlite3_stmt *pStmt = 0; int rc = SQLITE_OK; int bZeroBase = 0; /* True to set base.vector=0 */ Vec1Writer *p = 0; /* Write buffer object */ Vec1JobQueue *pQueue = 0; /* Job queue for multi-threaded rebuilds */ Vec1QuantizeJob *pQJ = 0; int nRow = 0; i64 iFirstInRange = VEC1_SMALLEST_INT64; assert( pTab->cfg.nElem==(int)pTab->mod.hdr.nElem || pTab->mod.hdr.nElem==0 ); /* Clear any existing ANN index */ rc = vec1SqlExec(pTab, "DELETE FROM %Q.'%q_idx'", 0); if( rc==SQLITE_OK ){ rc = vec1SqlExec(pTab, "DELETE FROM %Q.'%q_meta'", 0); } if( rc!=SQLITE_OK || (pTab->mod.hdr.flags & VEC1_MODEL_INDEX)==0 ) return rc; /* Allocate a writer object. Create transposed centroids if required. */ rc = vec1WriterAlloc(pTab, 1, &p); if( rc==SQLITE_OK ){ rc = vec1ModelAllocTCent(&pTab->mod); } /* If multiple threads have been configured and the model is not 'flat', ** create a job queue so that the rebuild can use multiple threads. There ** is nothing for background threads to do for 'flat' indexes. */ if( vec1ModelIsFlat(&pTab->mod) && pTab->mod.hdr.nBucket==0 ){ bZeroBase = 1; } #if VEC1_THREADS if( rc==SQLITE_OK && nThread>1 && bZeroBase==0 ){ pQueue = vec1JobQueueNew(nThread-1); if( pQueue==0 ){ rc = SQLITE_NOMEM; } } #endif /* VEC1_THREADS */ if( rc==SQLITE_OK ){ rc = vec1GetSql(pTab, VEC1_SQL_SCAN_BASE, &pStmt); } while( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pStmt) ){ i64 id = sqlite3_column_int64(pStmt, 0); int nVec = 0; const float *aVec = 0; nRow++; if( bZeroBase && nRow==VEC1_ROWS_PER_BASE_UPDATE ){ rc = vec1UpdateBaseRows(pTab, iFirstInRange, id); if( rc!=SQLITE_OK ) break; iFirstInRange = id+1; nRow = 0; } nVec = sqlite3_column_bytes(pStmt, 1); aVec = (const float*)sqlite3_column_blob(pStmt, 1); if( nVec!=(sizeof_f32*nElem) ){ rc = SQLITE_ERROR; vec1VtabError( pTab, "vec1: unexpected vector size in %q_base: %d", pTab->zName, nVec ); }else{ if( bZeroBase ){ int iBucket = -1; /* Must be -1 for vec1WriterVector()! */ rc = vec1WriterVector(p, id, aVec, &iBucket); if( rc==SQLITE_OK ){ rc = vec1WriterMetaFromStmt(p, iBucket, pStmt); } }else{ if( pQJ==0 ){ int nEncoded = vec1EncodedVectorSize(&pTab->mod); int nByte = sizeof(Vec1QuantizeJob) + sizeof(i64) * VEC1_QUANTIZE_JOB_SZ + /* aRowid */ pTab->nTmpVec * sizeof_f32 * VEC1_MULTIMATCH_NVEC +/* aTmp */ nElem * sizeof_f32 * VEC1_QUANTIZE_JOB_SZ + /* aVec */ sizeof(int) * VEC1_QUANTIZE_JOB_SZ + /* aBucket */ nEncoded * VEC1_QUANTIZE_JOB_SZ; /* aCode */ pQJ = (Vec1QuantizeJob*)sqlite3_malloc(nByte); if( pQJ==0 ){ rc = SQLITE_NOMEM; break; }else{ memset(pQJ, 0, sizeof(Vec1QuantizeJob)); pQJ->pModel = &pTab->mod; pQJ->pWriter = p; pQJ->aRowid = (i64*)&pQJ[1]; pQJ->aTmp = (float*)&pQJ->aRowid[VEC1_QUANTIZE_JOB_SZ]; pQJ->aVec = &pQJ->aTmp[pTab->nTmpVec * VEC1_MULTIMATCH_NVEC]; pQJ->aBucket = (int*)&pQJ->aVec[nElem * VEC1_QUANTIZE_JOB_SZ]; pQJ->aCode = (u8*)&pQJ->aBucket[VEC1_QUANTIZE_JOB_SZ]; pQJ->nEncoded = nEncoded; memset(pQJ->aTmp, 0, sizeof_f32*pTab->nTmpVec*VEC1_MULTIMATCH_NVEC); } } memcpy(&pQJ->aVec[nElem * pQJ->nVector], aVec, nVec); pQJ->aRowid[pQJ->nVector] = id; pQJ->nVector++; rc = vec1PackMetaValues(pTab, &pQJ->meta, pStmt); if( rc==SQLITE_OK && pQJ->nVector==VEC1_QUANTIZE_JOB_SZ ){ rc = vec1JobQueueAddJob( pQueue, vec1QuantizeJob, vec1QuantizeJobFinish, (void*)pQJ ); pQJ = 0; } } } } if( rc==SQLITE_OK && bZeroBase ){ rc = vec1UpdateBaseRows(pTab, iFirstInRange, VEC1_LARGEST_INT64); } if( pQJ ){ if( rc==SQLITE_OK ){ rc = vec1JobQueueAddJob( pQueue, vec1QuantizeJob, vec1QuantizeJobFinish, (void*)pQJ ); }else{ vec1BufferFree(&pQJ->meta); sqlite3_free(pQJ); } } rc = vec1JobQueueFinishJobs(pQueue, rc); vec1JobQueueFree(pQueue); rc = vec1WriterFinish(p, rc); vec1StmtReset(&rc, pStmt); vec1ModelFreeTCent(&pTab->mod); return rc; } /* ** The table currently has a flat index installed - an index with ** nCodesize==0 - and bucket numbers instead of vectors in the %_base ** table. This function scans the index, copying full vectors back into ** the %_base table and deleting %_idx entries as it goes. ** ** Return SQLITE_OK if successful, or an SQLite error code otherwise. */ static int vec1FlatToNone(Vec1Tab *pTab){ int rc = SQLITE_OK; sqlite3_stmt *pStmt = 0; sqlite3_stmt *pUp = 0; sqlite3_stmt *pDel = 0; rc = vec1GetSql(pTab, VEC1_SQL_SCAN_IDX, &pStmt); if( rc==SQLITE_OK ){ rc = vec1GetSql(pTab, VEC1_SQL_UPDATE_BASE, &pUp); } if( rc==SQLITE_OK ){ rc = vec1GetSql(pTab, VEC1_SQL_DELETE_FROM_IDX, &pDel); } while( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pStmt) ){ Vec1FlatIter iter; const u8 *aBlob = (const u8*)sqlite3_column_blob(pStmt, 0); int nBlob = sqlite3_column_bytes(pStmt, 0); i64 iRowid = sqlite3_column_int64(pStmt, 2); rc = vec1FlatIterStart(pTab, &iter, aBlob, nBlob); while( rc==SQLITE_OK && iter.aVec!=0 ){ rc = sqlite3_bind_blob(pUp, 1, iter.aVec, iter.szVec, SQLITE_STATIC); sqlite3_bind_int64(pUp, 2, iter.iRowid); sqlite3_step(pUp); vec1StmtReset(&rc, pUp); vec1FlatIterNext(&iter); } if( rc==SQLITE_OK ){ sqlite3_bind_int64(pDel, 1, iRowid); sqlite3_step(pDel); vec1StmtReset(&rc, pDel); } } if( pUp ) sqlite3_clear_bindings(pUp); vec1StmtReset(&rc, pStmt); return rc; } /* ** Symbols for the commands the vtab accepts. These must match the array ** in vec1InterpretCmdName(). */ #define VEC1_CMD_BLOCKSIZE 0 #define VEC1_CMD_REBUILD 1 #define VEC1_CMD_BLOCKSIZE_MIN 2 static int vec1InterpretCmdName(Vec1Tab *pTab, const char *zCmd, int *piCmd){ const char *azCmd[] = { "blocksize", "rebuild", "blocksize-min", 0 }; int iCmd; for(iCmd=0; iCmd=iMin && iVal<=iMax ) return SQLITE_OK; } vec1VtabError( pTab, "'%s' requires an integer value between %lld and %lld", zName, iMin, iMax ); return SQLITE_ERROR; } static int vec1WriteConfigI64(Vec1Tab *pTab, int iField, i64 iVal){ sqlite3_stmt *pStmt = 0; int rc = SQLITE_OK; rc = vec1GetSql(pTab, VEC1_SQL_WRITE_CONFIG, &pStmt); if( rc==SQLITE_OK ){ sqlite3_bind_int(pStmt, 1, iField); sqlite3_bind_int64(pStmt, 2, iVal); sqlite3_step(pStmt); rc = sqlite3_reset(pStmt); } return rc; } /* ** Fix the vector size to nVec *bytes*. This is done when the first ** vector is inserted into the table. Return SQLITE_OK if successful, or ** an SQLite error code if an error occurs. */ static int vec1FixVectorSize(Vec1Tab *pTab, int nVec){ int rc = SQLITE_OK; if( (nVec % sizeof_f32) || (nVec<(VEC1_VECSIZE_MIN*sizeof_f32)) || (nVec>(VEC1_VECSIZE_MAX*sizeof_f32)) ){ vec1VtabError(pTab, "invalid vector size: %d bytes", nVec); rc = SQLITE_ERROR; }else{ int nElem = nVec/sizeof_f32; rc = vec1WriteConfigI64(pTab, VEC1_CONFIG_NELEM, nElem); if( rc==SQLITE_OK ) pTab->cfg.nElem = nElem; } return rc; } /* ** Handle a "special" INSERT command on a vec1 table. A special INSERT takes ** the form: ** ** INSERT INTO vec1_table(cmd, vector) VALUES('cmd-name', pArg); ** ** The zCmd argument to this function is the text value specified for column ** "cmd" in the INSERT statement. Parameter pArg is the pArg value from the ** INSERT. ** ** Return SQLITE_OK if successful, or an SQLite error code otherwise. If ** an error code is returned, an error message may be left in pTab. */ static int vec1SpecialInsert( Vec1Tab *pTab, const char *zCmd, sqlite3_value *pArg ){ int rc = SQLITE_OK; int iCmd = 0; rc = vec1InterpretCmdName(pTab, zCmd, &iCmd); if( rc==SQLITE_OK ){ switch( iCmd ){ case VEC1_CMD_BLOCKSIZE: { rc = vec1InterpretInteger( pTab, pArg, "blocksize", VEC1_BLOCKSIZE_MIN, VEC1_BLOCKSIZE_MAX ); if( rc==SQLITE_OK ){ rc = vec1WriteConfigI64( pTab, VEC1_CONFIG_BLOCKSIZE, sqlite3_value_int64(pArg) ); } break; }; case VEC1_CMD_BLOCKSIZE_MIN: { rc = vec1InterpretInteger( pTab, pArg, "blocksize-min", VEC1_BLOCKSIZE_MIN, VEC1_BLOCKSIZE_MAX ); if( rc==SQLITE_OK ){ rc = vec1WriteConfigI64( pTab, VEC1_CONFIG_BLOCKSIZE_MIN, sqlite3_value_int64(pArg) ); } break; } default: assert( iCmd==VEC1_CMD_REBUILD ); { /* TODO: Should this be disallowed if there are active readers? */ if( rc==SQLITE_OK && sqlite3_value_type(pArg)!=SQLITE_NULL ){ u8 aFlat[VEC1_HEADER_SIZE]; Vec1Model mod; int nByte = 0; const u8 *aBlob = 0; sqlite3_stmt *pStmt = 0; memset(&mod, 0, sizeof(mod)); if( sqlite3_value_type(pArg)==SQLITE_BLOB ){ nByte = sqlite3_value_bytes(pArg); aBlob = sqlite3_value_blob(pArg); }else{ Vec1FlatIndex idx = {0, 0}; const char *zText = (const char*)sqlite3_value_text(pArg); char *zErr = 0; idx.eIndex = 0; idx.eDistance = VEC1_DISTANCE_L2; rc = vec1ParseJsonConfig( pTab->db, zText, vec1FlatIndexCfg, &idx, &zErr ); if( rc==SQLITE_OK && idx.eIndex==0 ){ zErr = sqlite3_mprintf("required parameter missing: index"); rc = SQLITE_ERROR; } if( zErr!=0 ){ vec1VtabError(pTab, "vec1: %z", zErr); } if( rc==SQLITE_OK ){ vec1HeaderWrite(aFlat, ((idx.eIndex==VEC1_INDEX_FLAT) ? VEC1_MODEL_INDEX : 0), 0, 0, 0, idx.eDistance ); aBlob = aFlat; nByte = VEC1_HEADER_SIZE; } } /* Decode the model blob here. Just to check if doing so causes any ** errors. If it does, refuse to store it. */ if( rc==SQLITE_OK ){ rc = vec1DecodeModel(aBlob, nByte, &mod, &pTab->base.zErrMsg); } if( rc==SQLITE_OK && mod.hdr.nElem>0 ){ if( pTab->cfg.nElem==0 ){ rc = vec1FixVectorSize(pTab, mod.hdr.nElem*sizeof_f32); }else if( pTab->cfg.nElem!=(int)mod.hdr.nElem ){ vec1VtabError(pTab, "vec1: model/data vector size mismatch: %d/%d", (int)mod.hdr.nElem, pTab->cfg.nElem ); rc = SQLITE_ERROR; } } /* Store the new model in the %_model table. */ if( rc==SQLITE_OK ){ const char zStore[] = "REPLACE INTO %Q.'%q_model' VALUES(1, ?)"; rc = vec1PrepareSql(pTab, &pStmt, zStore, pTab->zDb, pTab->zName); } if( rc==SQLITE_OK ){ rc = sqlite3_bind_blob(pStmt, 1, aBlob, nByte, SQLITE_STATIC); sqlite3_step(pStmt); vec1StmtFinalize(&rc, pStmt); } /* Increment (or create) the model version number in %_config */ if( rc==SQLITE_OK ){ rc = vec1GetSql(pTab, VEC1_SQL_INCR_CONFIG, &pStmt); } if( rc==SQLITE_OK ){ sqlite3_bind_int(pStmt, 1, VEC1_CONFIG_MODEL); sqlite3_step(pStmt); rc = sqlite3_reset(pStmt); } } if( rc==SQLITE_OK && vec1ModelIsFlat(&pTab->mod) ){ rc = vec1FlatToNone(pTab); } if( rc==SQLITE_OK ){ rc = vec1LoadConfig(pTab); } if( rc==SQLITE_OK ){ rc = vec1RebuildIndex(pTab, pTab->pTabList->nThread); } } } } return rc; } /* ** The maximum permitted number of tombstone entries in list containing ** nEntry entries. If this threshold is exceeded, the list is reorganized ** to reduce the number of tombstones to 0. */ #define VEC1_TOMBSTONE_THRESHOLD(nEntry) ((nEntry * 2) / 10) /* ** Locate the index entry for row iRowid, for writing. Array aVec[] is the ** row's transformed vector. ** ** The bucket selected for aVec[] by vec1BestMatchSimple() is searched ** first. If the model has no buckets, this is bucket 0 and it is the only ** bucket searched. Otherwise, if the row is not found there, it may have ** been assigned to a different bucket by code that rounds differently - ** either a different routine, such as the batched routines used by ** 'rebuild', or the same routine on a different platform. In that case ** all other buckets that are near-ties with the selected bucket (see ** vec1BucketIsNearTie()) are searched, followed by all remaining buckets. ** ** If the row is found, (*pLoc) is populated and (*piBucket) is set to ** the bucket it was found in. If it is not found, (*pLoc) is zeroed and ** SQLITE_OK returned. The caller must then treat the row as missing. */ static int vec1DeleteFindRowid( Vec1Tab *pTab, /* Table to search */ const float *aVec, /* Transformed vector */ i64 iRowid, /* Rowid to locate */ int *piBucket, /* OUT: Bucket containing iRowid */ Vec1RowidLocation *pLoc /* OUT: Location of iRowid */ ){ const Vec1Model *pMod = &pTab->mod; const int nElem = (int)pMod->hdr.nElem; const int nBucket = (int)pMod->hdr.nBucket; int iBest = 0; double fBest = 0.0; int rc = SQLITE_OK; int iPass; if( nBucket>0 ){ iBest = vec1BestMatchSimple(pMod->aCentroid, nBucket, aVec, nElem, &fBest); } rc = vec1FindByRowid(pTab, iBest, 1, iRowid, pLoc); if( rc!=SQLITE_OK || pLoc->pList || pLoc->pBlob ){ *piBucket = iBest; return rc; } /* Pass 0 searches buckets that are near-ties with iBest. Pass 1 searches ** all others. */ for(iPass=0; iPass<2; iPass++){ int ii; for(ii=0; iiaCentroid[ii*nElem], nElem); if( vec1BucketIsNearTie(fBest, fDist)==(iPass==0) ){ rc = vec1FindByRowid(pTab, ii, 1, iRowid, pLoc); if( rc!=SQLITE_OK || pLoc->pList || pLoc->pBlob ){ *piBucket = ii; return rc; } } } } } return SQLITE_OK; } /* ** Delete the %_base entry for the row with rowid=iRowid. Also zero-out ** the corresponding %_idx entry. */ static int vec1DeleteByRowid(Vec1Tab *pTab, i64 iRowid){ const u8 aTomb4[4] = {0xFF, 0xFF, 0xFF, 0xFF}; const u8 aTomb8[8] = {0, 0, 0, 0, 0, 0, 0, 0}; sqlite3_stmt *pDelete = 0; int rc = SQLITE_OK; rc = vec1GetSql(pTab, VEC1_SQL_DEL_LOOKUP_BASE, &pDelete); if( rc==SQLITE_OK ){ /* Delete the row from the %_base table. Also set variable iBucket to ** the index of the bucket containing the vector. Statement pDelete is ** not reset until the end of this function, as aTransform[] may point ** to the vector it returns. */ sqlite3_bind_int64(pDelete, 1, iRowid); if( (SQLITE_ROW==sqlite3_step(pDelete)) && (pTab->mod.hdr.flags & VEC1_MODEL_INDEX) ){ int iBucket = 0; Vec1RowidLocation loc; if( pTab->mod.hdr.nCodebook==0 ){ iBucket = sqlite3_column_int(pDelete, 0); rc = vec1FindByRowid(pTab, iBucket, 1, iRowid, &loc); }else{ const u8 *aVec = sqlite3_column_blob(pDelete, 0); int nVec = sqlite3_column_bytes(pDelete, 0); if( nVec!=(pTab->cfg.nElem*sizeof_f32) ){ rc = VEC1_CORRUPT; }else{ const float *aTransform = vec1TransformInputVector( &pTab->mod, pTab->aTmpVec, (const float*)aVec ); rc = vec1DeleteFindRowid(pTab, aTransform, iRowid, &iBucket, &loc); } } if( rc==SQLITE_OK ){ if( loc.pList==0 && loc.pBlob==0 ){ /* Missing row. This is an error. */ rc = VEC1_CORRUPT; }else if( (loc.nTombstone+1) > VEC1_TOMBSTONE_THRESHOLD(loc.nEntry) ){ /* This delete would cause the list's tombstone count to exceed ** the threshold. So reorganize the list and remove iRowid in ** one go. */ if( loc.pList ){ rc = vec1ListBuilderCompress(loc.pList, iRowid); }else{ /* Load the list into memory, compress it so there are no ** tombstones, then write it back to the database. */ Vec1ListBuilder list; int nVal = sqlite3_blob_bytes(loc.pBlob); u8 *aVal = sqlite3_malloc(nVal); memset(&list, 0, sizeof(list)); if( aVal==0 ){ rc = SQLITE_NOMEM; }else{ rc = sqlite3_blob_read(loc.pBlob, aVal, nVal, 0); } if( rc==SQLITE_OK ){ rc = vec1ListBuilderInit(&list, pTab, iBucket); } if( rc==SQLITE_OK ){ /* Initialize iFirst and iLast to 0. vec1ListBuilderCompress() ** will set the correct values. */ rc = vec1ListBuilderLoad(&list, loc.iIdx, 0, 0, aVal, nVal); } if( rc==SQLITE_OK ){ rc = vec1ListBuilderCompress(&list, iRowid); } if( rc==SQLITE_OK ){ rc = vec1ListBuilderFlush(&list); } vec1ListBuilderFree(&list); sqlite3_free(aVal); } }else{ /* Replace the rowid with a tombstone and increment the tombstone ** count. */ const u8 *aTomb = loc.szRowid==4 ? aTomb4 : aTomb8; if( loc.pList ){ int iOff = loc.iEntry * loc.szRowid; memcpy(&loc.pList->bufRowid.a[iOff], aTomb, loc.szRowid); loc.pList->nTombstone++; }else{ int iOff = VEC1_LIST_SZHDR + (loc.iEntry * loc.szRowid); rc = sqlite3_blob_write(loc.pBlob, aTomb, loc.szRowid, iOff); if( rc==SQLITE_OK ){ u8 aTS[4]; vec1PutU32(aTS, loc.nTombstone+1); rc = sqlite3_blob_write(loc.pBlob, aTS, sizeof(aTS), 8); } } } if( loc.pBlob ){ int rc2 = sqlite3_blob_close(loc.pBlob); if( rc==SQLITE_OK ) rc = rc2; } } } vec1StmtReset(&rc, pDelete); } return rc; } /* ** xUpdate ** ** argc/argv meanings: ** argc==1 DELETE ** argc>1 && argv[0]==NULL INSERT ** argc>1 && argv[0]!=NULL UPDATE ** ** argv[0] = old rowid (or NULL) ** argv[1] = new rowid (or NULL to auto-assign) ** argv[2+] = column values */ static int vec1UpdateMethod( sqlite3_vtab *pVtab, int argc, sqlite3_value **argv, sqlite3_int64 *pRowid ){ int rc = SQLITE_OK; Vec1Tab *pTab = (Vec1Tab*)pVtab; const int iCmd = 2 + VEC1_COLUMN_CMD; int bDeleteRow = (argc==1); /* Delete row at end of this function */ /* Ensure the configuration has been loaded. */ if( (rc = vec1LoadConfigIfRequired(pTab))!=SQLITE_OK ) return rc; if( argc>1 ){ int nVec = sqlite3_value_bytes(argv[2+VEC1_COLUMN_VECTOR]); const void *aVec = sqlite3_value_blob(argv[2+VEC1_COLUMN_VECTOR]); int eType = sqlite3_value_type(argv[2+VEC1_COLUMN_VECTOR]); /* Check for a special INSERT. Handle these separately. */ if( sqlite3_value_type(argv[iCmd])!=SQLITE_NULL ){ const char *zCmd = (char*)sqlite3_value_text(argv[iCmd]); sqlite3_value *pVal = argv[2+VEC1_COLUMN_ARG]; if( sqlite3_value_type(pVal)==SQLITE_NULL ){ pVal = argv[2+VEC1_COLUMN_VECTOR]; } return vec1SpecialInsert(pTab, zCmd, pVal); } /* Check that the supplied vector is the right type and size. If this ** is the first vector written to this table, fix the vector size ** now. */ if( eType!=SQLITE_BLOB ){ rc = vec1VectorTypeError(pTab, eType); }else if( pTab->cfg.nElem>0 ){ if( nVec!=pTab->cfg.nElem*sizeof_f32 ){ rc = vec1VectorSizeError(pTab, nVec); } }else{ rc = vec1FixVectorSize(pTab, nVec); } /* Check that the new vector does not contain any NaN or Infinity values */ if( rc==SQLITE_OK ){ rc = vec1VectorIsNonFinite((float*)aVec, pTab->cfg.nElem); if( rc!=SQLITE_OK ){ vec1VtabError(pTab, "vec1: non-finite value not allowed in vector"); } } /* If this is an UPDATE that does not modify the rowid, then delete ** the existing row now. Or, if it is an UPDATE that does modify ** the rowid, schedule deletion of the existing row at the end ** of this function. */ if( rc==SQLITE_OK && sqlite3_value_type(argv[0])==SQLITE_INTEGER ){ if( sqlite3_value_int64(argv[0])==sqlite3_value_int64(argv[1]) ){ rc = vec1DeleteByRowid(pTab, sqlite3_value_int64(argv[0])); }else{ bDeleteRow = 1; } } /* If this is an INSERT or UPDATE, write the new row. */ if( rc==SQLITE_OK ){ int iBucket = 0; /* If there is an index (i.e. not index:"none"), then allocate an ** index writer. */ if( rc==SQLITE_OK && (pTab->mod.hdr.flags & VEC1_MODEL_INDEX) && pTab->pWriter==0 ){ rc = vec1WriterAlloc(pTab, 0, &pTab->pWriter); assert( rc!=SQLITE_OK || pTab->pWriter!=0 ); } /* Write to the %_base table. */ if( rc==SQLITE_OK ){ sqlite3_stmt *pInsert = 0; /* If required, quantize the vector. This is because in ** some configurations, the bucket is written into the %_base ** table, so it must be determined now, before the base table entry ** is written */ if( pTab->pWriter ){ Vec1Writer *p = pTab->pWriter; vec1QuantizeVector(&pTab->mod, p->aResidual, aVec, &iBucket, p->aPQ); } *pRowid = 0; rc = vec1GetSql(pTab, VEC1_SQL_INSERT_BASE, &pInsert); if( rc==SQLITE_OK ){ int ii; sqlite3_bind_value(pInsert, 1, argv[1]); if( vec1ModelIsFlat(&pTab->mod) ){ sqlite3_bind_int(pInsert, 2, iBucket); }else{ sqlite3_bind_blob(pInsert, 2, aVec, nVec, SQLITE_STATIC); } for(ii=0; iinMeta; ii++){ sqlite3_bind_value(pInsert, 3+ii, argv[2+VEC1_COLUMN_VECTOR+1+ii]); } sqlite3_step(pInsert); rc = sqlite3_reset(pInsert); *pRowid = sqlite3_last_insert_rowid(pTab->db); } } /* If the table has been trained, write the %_idx entry. And, if ** there are any meta-data columns, the %_meta entries as well. */ if( pTab->mod.hdr.flags & VEC1_MODEL_INDEX ){ if( rc==SQLITE_OK ){ rc = vec1WriterVector(pTab->pWriter, *pRowid, aVec, &iBucket); } if( rc==SQLITE_OK ){ sqlite3_value **aMeta = &argv[2+VEC1_COLUMN_VECTOR+1]; rc = vec1WriterMetaFromArray(pTab->pWriter, iBucket, aMeta); } } } } if( rc==SQLITE_OK && bDeleteRow ){ rc = vec1DeleteByRowid(pTab, sqlite3_value_int64(argv[0])); } return rc; } static int vec1FinishWriter(Vec1Tab *pTab, int bDiscard){ int rc = SQLITE_OK; if( bDiscard ){ vec1WriterFinish(pTab->pWriter, SQLITE_ERROR); }else{ rc = vec1WriterFinish(pTab->pWriter, SQLITE_OK); } pTab->pWriter = 0; return rc; } static int vec1SyncMethod(sqlite3_vtab *pVtab){ int rc = SQLITE_OK; Vec1Tab *pTab = (Vec1Tab*)pVtab; if( (rc = vec1LoadConfigIfRequired(pTab))!=SQLITE_OK ) return rc; return vec1FinishWriter(pTab, 0); } static int vec1BeginMethod(sqlite3_vtab *pVtab){ Vec1Tab *pTab = (Vec1Tab*)pVtab; int rc = SQLITE_OK; rc = vec1LoadConfig(pTab); return rc; } /* ** xRename method for vec1 virtual table. This renames all shadow tables ** and the %_idx_idx index on the %_idx table. */ static int vec1RenameMethod(sqlite3_vtab *pVtab, const char *zNew){ const char zFmt[] = "ALTER TABLE %Q.'%q_config' RENAME TO '%q_config';" "ALTER TABLE %Q.'%q_base' RENAME TO '%q_base';" "ALTER TABLE %Q.'%q_idx' RENAME TO '%q_idx';" "ALTER TABLE %Q.'%q_model' RENAME TO '%q_model';" "ALTER TABLE %Q.'%q_meta' RENAME TO '%q_meta';" "UPDATE %Q.sqlite_schema SET name = '%q_idx_idx', sql=" " 'CREATE INDEX ' || quote('%q_idx_idx') || " " ' ON ' || quote('%q_idx') || '(bucket, first, last)'" "WHERE type='index' AND name='%q_idx_idx';" ; Vec1Tab *pTab = (Vec1Tab*)pVtab;/* Virtual table object */ int rc = SQLITE_OK; /* Return value */ int bDefensive = 0; /* Initial value of DEFENSIVE */ int bWritable = 0; /* Initial value of WRITABLE_SCHEMA */ char *zSql = 0; /* SQL script to rename shadow objects */ /* Query the initial values of the DEFENSIVE and WRITABLE_SCHEMA db-config ** options. Then clear DEFENSIVE and set WRITABLE_SCHEMA so that we can ** UPDATE the sqlite_schema entry of the index directly. */ sqlite3_db_config(pTab->db, SQLITE_DBCONFIG_DEFENSIVE, -1, &bDefensive); sqlite3_db_config(pTab->db, SQLITE_DBCONFIG_WRITABLE_SCHEMA, -1, &bWritable); sqlite3_db_config(pTab->db, SQLITE_DBCONFIG_DEFENSIVE, 0, (int*)0); sqlite3_db_config(pTab->db, SQLITE_DBCONFIG_WRITABLE_SCHEMA, 1, (int*)0); /* Run the SQL script to rename the shadow tables and index */ zSql = sqlite3_mprintf(zFmt, pTab->zDb, pTab->zName, zNew, /* %_config */ pTab->zDb, pTab->zName, zNew, /* %_base */ pTab->zDb, pTab->zName, zNew, /* %_idx */ pTab->zDb, pTab->zName, zNew, /* %_model */ pTab->zDb, pTab->zName, zNew, /* %_meta */ pTab->zDb, zNew, zNew, zNew, pTab->zName ); if( zSql==0 ){ rc = SQLITE_NOMEM; }else{ rc = sqlite3_exec(pTab->db, zSql, 0, 0, &pTab->base.zErrMsg); sqlite3_free(zSql); } /* Restore the values of DEFENSIVE and WRITABLE_SCHEMA */ sqlite3_db_config(pTab->db,SQLITE_DBCONFIG_DEFENSIVE, bDefensive, (int*)0); sqlite3_db_config(pTab->db,SQLITE_DBCONFIG_WRITABLE_SCHEMA,bWritable,(int*)0); return rc; } static int vec1RollbackMethod(sqlite3_vtab *pVtab){ Vec1Tab *pTab = (Vec1Tab*)pVtab; vec1UnloadModel(pTab); return vec1FinishWriter(pTab, 1); } static int vec1SavepointMethod(sqlite3_vtab *pVtab, int iSavepoint){ Vec1Tab *pTab = (Vec1Tab*)pVtab; int rc = SQLITE_OK; UNUSED_PARAMETER(iSavepoint); if( (rc = vec1LoadConfigIfRequired(pTab))!=SQLITE_OK ) return rc; return vec1FinishWriter(pTab, 0); } static int vec1ReleaseMethod(sqlite3_vtab *pVtab, int iSavepoint){ Vec1Tab *pTab = (Vec1Tab*)pVtab; int rc = SQLITE_OK; UNUSED_PARAMETER(iSavepoint); if( (rc = vec1LoadConfigIfRequired(pTab))!=SQLITE_OK ) return rc; return vec1FinishWriter(pTab, 0); } static int vec1RollbackToMethod(sqlite3_vtab *pVtab, int iSavepoint){ Vec1Tab *pTab = (Vec1Tab*)pVtab; UNUSED_PARAMETER(iSavepoint); vec1FinishWriter(pTab, 1); vec1UnloadConfig(pTab); return SQLITE_OK; } /* ** This function is used by the integrity-check code to handle meta-data. ** Argument pBuf contains a meta-data record, complete with VEC1_META_SZHDR ** byte header. When this function is called, (*piOff) contains the offset ** into this buffer of the next meta-data value to read. ** ** When called, this function reads one meta-data value from (*piOff) and ** advances (*piOff) to point to the next value. If pVal is NULL, it returns ** SQLITE_OK at that point. Or, if pVal is not NULL, it is compared to ** the value just read. If they match, SQLITE_OK is returned. Otherwise, ** SQLITE_ERROR. */ static int vec1IntegrityMetaValue( Vec1Buffer *pBuf, int *piOff, sqlite3_value *pVal ){ u32 f = vec1GetU32(pBuf->a); int iOff = *piOff; Vec1MetaValue val; int rc = SQLITE_OK; memset(&val, 0, sizeof(val)); if( (f & VEC1_META_1BYTEINT) ){ val.iVal = pBuf->a[iOff++]; if( val.iVal==VEC1_META_1BYTENULL ){ val.eType = SQLITE_NULL; }else{ val.eType = SQLITE_INTEGER; } }else if( (f & VEC1_META_4BYTEINT) ){ val.iVal = (int)vec1GetU32(&pBuf->a[iOff]); iOff += sizeof_u32; if( (u32)val.iVal==VEC1_META_4BYTENULL ){ val.eType = SQLITE_NULL; }else{ val.eType = SQLITE_INTEGER; } }else if( (f & VEC1_META_REAL) ){ u64 v = vec1GetU64(&pBuf->a[iOff]); iOff += sizeof_f64; if( v==VEC1_META_REALNULL ){ val.eType = SQLITE_NULL; }else{ val.eType = SQLITE_FLOAT; memcpy(&val.fVal, &v, sizeof_f64); } }else{ vec1MetaValueRead(pBuf, &iOff, &val); } if( rc==SQLITE_OK && pVal ){ int eType = sqlite3_value_type(pVal); int bMatch = 0; switch( eType ){ case SQLITE_NULL: bMatch = (val.eType==SQLITE_NULL); break; case SQLITE_INTEGER: { i64 iVal = sqlite3_value_int64(pVal); bMatch = ( (val.eType==SQLITE_INTEGER && val.iVal==iVal) || (val.eType==SQLITE_FLOAT && val.fVal==(double)iVal) ); break; } case SQLITE_FLOAT: { double fVal = sqlite3_value_double(pVal); bMatch = (val.eType==SQLITE_FLOAT && val.fVal==fVal); break; } case SQLITE_TEXT: { int nVal = sqlite3_value_bytes(pVal); if( val.eType==SQLITE_TEXT && val.iVal==nVal ){ const char *zVal = (const char*)sqlite3_value_text(pVal); bMatch = (0==memcmp(val.pPtr, zVal, nVal)); } break; } default: { int nVal = sqlite3_value_bytes(pVal); if( val.eType==SQLITE_BLOB && val.iVal==nVal ){ const u8 *zVal = (const u8*)sqlite3_value_blob(pVal); bMatch = (0==memcmp(val.pPtr, zVal, nVal)); } assert( eType==SQLITE_BLOB ); break; } } if( bMatch==0 ) rc = SQLITE_ERROR; } *piOff = iOff; return rc; } /* ** Return true if the difference between f1 and f2 is smaller than ** (fTol * max(f1, f2)). Or false if they differ by more than this. */ static int vec1FloatWithinTolerance(float f1, float f2, float fTol){ if( f1==0.0 || f2==0.0 ) return 0; if( f2>f1 ){ if( ((f2-f1) / f2) > fTol ) return 0; }else{ if( ((f1-f2) / f1) > fTol ) return 0; } return 1; } /* ** Check that the encoded version of aVec[] matches the encoded vector ** stored at entry iEntry of aBlob[]. For BQ encodings, the bit-quantized ** vector must match exactly and the stored norm must be within a small ** tolerance of the calculated value. For PQ encodings, the encoded vectors ** are compared using their reconstruction errors, allowing for small ** differences in floating-point arithmetic. ** ** Return non-zero if the encoded vector is inconsistent with aVec[] or if ** an error occurs. Set *pRc to SQLITE_NOMEM if temporary space cannot be ** allocated. */ static int vec1IntegrityCheckEncoded( int *pRc, /* IN/OUT: Error code */ Vec1Model *pMod, /* Model used to quantize vectors */ const float *aVec, /* Vector to compare quantized entry to */ int iEntry, /* Entry of aBlob[] to compare to */ const u8 *aBlob, /* %_idx.val value */ Vec1Buffer *pBuf /* Buffer to use for temp space */ ){ const int nEnc = vec1EncodedVectorSize(pMod); const int nElem = (int)pMod->hdr.nElem; /* Allocate the maximum amount of space required by this function - ** enough for one full vector and two encoded vectors. */ int nReq = (nElem * sizeof_f32) + nEnc*2; if( vec1BufferSize(pBuf, nReq) ){ *pRc = SQLITE_NOMEM; return 1; }else{ const int nCodebook = pMod->hdr.nCodebook; float *v1 = (float*)pBuf->a; /* Space for decoded vector */ u8 *aEnc = (u8*)&v1[nElem]; /* Encoded version of aVec[] */ u8 *aIdx = &aEnc[nEnc]; /* Encoded vector read from aBlob[] */ u32 flags = vec1GetU32(&aBlob[0]); u32 nEntry = (int)vec1GetU32(&aBlob[4]); int iOut = 0; int iOff; int ii; /* Encode the vector into aEnc */ vec1EncodeVector(pMod, aVec, aEnc); /* Read the encoded version of the vector from aBlob[] into aIdx[] */ iOff = nEntry * ((flags & VEC1_LIST_64BIT) ? 8 : 4) + VEC1_LIST_SZHDR; if( pMod->hdr.flags & VEC1_MODEL_BITQUANT ){ int iNorm = iOff + VEC1_LIST_NORM_OFFSET(iEntry, nCodebook); memcpy(aIdx, &aBlob[iNorm], sizeof_f32); iOff += VEC1_LIST_CODE_OFFSET(iEntry, nCodebook); iOut = sizeof_f32; }else{ iOff += VEC1_LIST_BLOCKED_OFFSET(iEntry, nCodebook); } for(ii=0; iihdr.flags & VEC1_MODEL_BITQUANT ){ float norm1; float norm2; memcpy(&norm1, aEnc, sizeof_f32); memcpy(&norm2, aIdx, sizeof_f32); /* Check the norms are close enough. If not, return 1. */ if( vec1FloatWithinTolerance(norm1, norm2, fTol)==0 ) return 1; /* Check the arrays of bits are identical. If not, return 1. */ if( memcmp(&aEnc[sizeof_f32], &aIdx[sizeof_f32], nEnc-sizeof_f32) ){ return 1; } }else{ float e1 = 0.0; /* aEnc[] squared reconstruction error */ float e2 = 0.0; /* aIdx[] squared reconstruction error */ vec1DecodeVector(pMod, aEnc, v1); e1 = (float)vec1L2Dist(v1, aVec, nElem); vec1DecodeVector(pMod, aIdx, v1); e2 = (float)vec1L2Dist(v1, aVec, nElem); if( vec1FloatWithinTolerance(e1, e2, fTol)==0 ) return 1; } } } return 0; } /* ** Check that a vector with transformed value aVec[] may legitimately be ** stored in bucket iBucket. ** ** If vec1BestMatchSimple() selects bucket iBucket for aVec[], then it ** may. Otherwise, the vector may have been assigned to its bucket by ** different code - either a different routine, such as the batched ** routines used by 'rebuild', or the same routine on a different platform. ** Rounding differences mean such code may select a different bucket when ** aVec[] is (almost) equidistant from two or more centroids. So iBucket ** is also accepted if the distance from aVec[] to centroid iBucket is ** within a small tolerance of the distance to the centroid selected by ** vec1BestMatchSimple(). ** ** Return non-zero if aVec[] may not be stored in bucket iBucket, or zero ** otherwise. */ static int vec1IntegrityCheckBucket( const Vec1Model *pMod, /* Model used to quantize vectors */ const float *aVec, /* Transformed vector */ int iBucket /* Bucket the vector is stored in */ ){ const int nElem = (int)pMod->hdr.nElem; const int nBucket = (int)pMod->hdr.nBucket; double fBest = 0.0; double fDist = 0.0; int iBest = 0; if( nBucket==0 ) return (iBucket!=0); if( iBucket<0 || iBucket>=nBucket ) return 1; iBest = vec1BestMatchSimple(pMod->aCentroid, nBucket, aVec, nElem, &fBest); if( iBest==iBucket ) return 0; fDist = vec1L2Dist(aVec, &pMod->aCentroid[iBucket*nElem], nElem); return vec1BucketIsNearTie(fBest, fDist)==0; } /* ** The buffer passed as the only argument contains a meta-value array. If ** it is of type GENERIC, test that it is well-formed. Return VEC1_CORRUPT ** if it is not, or SQLITE_OK if it looks ok. */ static int vec1IntegrityCheckMetaArray(Vec1Buffer *pBuf){ int rc = SQLITE_OK; int bGeneric = vec1GetU32(pBuf->a) & VEC1_META_GENERIC; if( bGeneric ){ u32 nEntry = vec1GetU32(&pBuf->a[4]); u32 ii; int iOff = VEC1_META_SZHDR; for(ii=0; rc==SQLITE_OK && iimod.hdr.flags & VEC1_MODEL_INDEX) ){ int bFlat = vec1ModelIsFlat(&pTab->mod); Vec1Model *pMod = &pTab->mod; const int nCodebook = pMod->hdr.nCodebook; const int nElem = pTab->cfg.nElem; int *aiOffMeta = 0; i64 nTotalEntry = 0; u8 *aIdxPQ = 0; if( nCodebook>0 && pMod->hdr.nBucket>0 ){ aResidual = vec1MallocZero(pTab->nTmpVec * sizeof_f32); if( aResidual==0 ){ rc = SQLITE_NOMEM; goto integrity_failed; } } if( nCodebook>0 ){ aPQ = (u8*)vec1MallocZero((nCodebook+sizeof_f32) * 2); if( aPQ==0 ){ rc = SQLITE_NOMEM; goto integrity_failed; } aIdxPQ = &aPQ[nCodebook + sizeof_f32]; } rc = vec1GetSql(pTab, VEC1_SQL_SCAN_IDX, &pScan); if( rc==SQLITE_OK ){ rc = vec1GetSql(pTab, VEC1_SQL_LOOKUP_BASE, &pLookup); } /* If there are one or more meta-value columns, allocate space for ** buffers to load each of them from the database */ if( rc==SQLITE_OK && pTab->nMeta>0 ){ int nByte = pTab->nMeta * (sizeof(Vec1Buffer) + sizeof(int)); aBufMeta = (Vec1Buffer*)vec1MallocZero(nByte); if( aBufMeta==0 ){ rc = SQLITE_NOMEM; }else{ aiOffMeta = (int*)&aBufMeta[pTab->nMeta]; } } while( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pScan) ){ const u8 *aBlob = sqlite3_column_blob(pScan, 0); int nBlob = sqlite3_column_bytes(pScan, 0); int iBucket = sqlite3_column_int(pScan, 1); i64 iId = sqlite3_column_int64(pScan, 2); int szRowid = 4; int nEntry = 0; u32 flags = 0; int nTombstone = 0; int ii = 0; int iMeta = 0; int nNonTombstone = 0; if( vec1CheckIdxSize(pTab, aBlob, nBlob) ){ const char *zFmt = "%s: %%_idx entry id=%lld is corrupt"; zErr = sqlite3_mprintf(zFmt, zTabName, sqlite3_column_int64(pScan, 2)); goto integrity_failed; } flags = vec1GetU32(&aBlob[0]); nEntry = (int)vec1GetU32(&aBlob[4]); nTombstone = (int)vec1GetU32(&aBlob[8]); if( flags & VEC1_LIST_64BIT ){ szRowid = 8; } /* Load the array for each meta-value column from disk */ for(iMeta=0; iMetanMeta; iMeta++){ rc = vec1ReadMeta(pTab, nEntry, &aBufMeta[iMeta], iId, iMeta); if( rc==SQLITE_OK ){ rc = vec1IntegrityCheckMetaArray(&aBufMeta[iMeta]); } if( rc!=SQLITE_OK ){ if( rc==SQLITE_CORRUPT_VTAB ){ const char *zFmt = "%s: error reading meta-list id=%lld,meta=%d"; zErr = sqlite3_mprintf(zFmt, zTabName, iId, iMeta); rc = SQLITE_OK; } goto integrity_failed; } aiOffMeta[iMeta] = VEC1_META_SZHDR; } for(ii=0; iinMeta; iMeta++){ vec1IntegrityMetaValue(&aBufMeta[iMeta], &aiOffMeta[iMeta], 0); } continue; } nNonTombstone++; sqlite3_bind_int64(pLookup, 1, iRowid); if( sqlite3_step(pLookup)==SQLITE_ROW ){ bExists = 1; /* Check that the meta-value arrays contain the correct data */ for(iMeta=0; iMetanMeta; iMeta++){ Vec1Buffer *pBuf = &aBufMeta[iMeta]; sqlite3_value *pVal = sqlite3_column_value(pLookup, 2+iMeta); if( vec1IntegrityMetaValue(pBuf, &aiOffMeta[iMeta], pVal) ){ const char *zFmt = "%s: meta-value value mismatch for row %lld"; zErr = sqlite3_mprintf(zFmt, zTabName, iRowid); goto integrity_failed; } } if( bFlat ){ int iBaseBucket = sqlite3_column_int(pLookup, 1); if( iBaseBucket!=iBucket ){ const char *zFmt = "%s: bucket value in %%_base row %lld is incorrect"; zErr = sqlite3_mprintf(zFmt, zTabName, iRowid); goto integrity_failed; } }else{ const u8 *aBaseVec = sqlite3_column_blob(pLookup, 1); const float *aTransform = 0; int nBaseVec = sqlite3_column_bytes(pLookup, 1); assert( nCodebook>0 ); /* Check the vector is the right size. */ if( nBaseVec!=nElem*sizeof_f32 ){ const char *zFmt = "%s: vector in %%_base row %lld is wrong size"; zErr = sqlite3_mprintf(zFmt, zTabName, iRowid); goto integrity_failed; } aTransform = vec1TransformInputVector( &pTab->mod, pTab->aTmpVec, (const float*)aBaseVec ); /* Check the vector is in the right bucket. */ if( vec1IntegrityCheckBucket(pMod, aTransform, iBucket) ){ const char *zFmt = "%s: vector from row %lld is in wrong bucket"; zErr = sqlite3_mprintf(zFmt, zTabName, iRowid); goto integrity_failed; } /* Check that the contents of the index entry look right. */ { /* Calculate a PQ code based on the transformed vector. Store ** this in aPQ[]. For binary quantization, aPQ[] contains the ** norm followed by nCodebook bytes of data. For PQ or OPQ, ** aPQ[] just contains the nCodebook bytes of encoded vector. */ const float *aEnc = (const float*)aTransform; if( (pMod->hdr.flags & VEC1_MODEL_RESIDUAL) ){ const float *aCent = &pMod->aCentroid[iBucket*nElem]; vec1Sub(aResidual, aEnc, aCent, nElem); aEnc = aResidual; } if( vec1IntegrityCheckEncoded(&rc, pMod, aEnc, ii, aBlob, &tmp) ){ const char *zType; const char *zFmt = "%s: %%_idx %s does not match calculated %s for row %lld"; zType = (pMod->hdr.flags & VEC1_MODEL_BITQUANT) ? "BQ" : "PQ"; zErr = sqlite3_mprintf(zFmt, zTabName, zType, zType, iRowid); goto integrity_failed; } } } } vec1StmtReset(&rc, pLookup); if( rc==SQLITE_OK && bExists==0 ){ const char *zFmt = "%s: %%_idx row %lld missing from %%_base"; zErr = sqlite3_mprintf(zFmt, zTabName, iRowid); goto integrity_failed; } } if( rc==SQLITE_OK && (nNonTombstone+nTombstone)!=nEntry ){ const char *zFmt = "%s: bad tombstone count"; zErr = sqlite3_mprintf(zFmt, zTabName); goto integrity_failed; } nTotalEntry += nNonTombstone; } if( rc==SQLITE_OK ){ i64 nGot = -1; sqlite3_stmt *pStmt = 0; const char *zCount = "SELECT count(*) FROM %Q.'%q_base'"; rc = vec1PrepareSql(pTab, &pStmt, zCount, pTab->zDb, pTab->zName); if( rc==SQLITE_OK ){ if( sqlite3_step(pStmt)==SQLITE_ROW ){ nGot = sqlite3_column_int64(pStmt, 0); } } vec1StmtFinalize(&rc, pStmt); if( rc==SQLITE_OK && nGot!=nTotalEntry ){ const char *zFmt = "%s: wrong number of entries in %%_base - have %lld, expect %lld"; zErr = sqlite3_mprintf(zFmt, zTabName, nGot, nTotalEntry); goto integrity_failed; } } } integrity_failed: *pzErr = zErr; sqlite3_free(aResidual); sqlite3_free(aPQ); vec1BufferFree(&tmp); if( aBufMeta ){ int ii; for(ii=0; iinMeta; ii++){ vec1BufferFree(&aBufMeta[ii]); } sqlite3_free(aBufMeta); } sqlite3_free(pTab->base.zErrMsg); pTab->base.zErrMsg = 0; vec1StmtReset(&rc, pScan); vec1StmtReset(&rc, pLookup); return rc; } /************************************************************************* ** Start of vec1_cat virtual table. */ #define VEC1CAT_SCHEMA \ "CREATE TABLE vec1cat(database, name, model, qprofile)" typedef struct vec1cat_vtab vec1cat_vtab; struct vec1cat_vtab { sqlite3_vtab base; Vec1TabList *pList; }; typedef struct vec1cat_cursor vec1cat_cursor; struct vec1cat_cursor { sqlite3_vtab_cursor base; sqlite3_int64 iRowid; }; static int vec1catConnectMethod( sqlite3 *db, void *pAux, int argc, const char *const *argv, sqlite3_vtab **ppVtab, char **pzErr ){ vec1cat_vtab *pNew; int rc; UNUSED_PARAMETER(pzErr); UNUSED_PARAMETER2(argv, argc); rc = sqlite3_declare_vtab(db, VEC1CAT_SCHEMA); if( rc==SQLITE_OK ){ pNew = vec1MallocZero(sizeof(*pNew)); *ppVtab = (sqlite3_vtab*)pNew; if( pNew==0 ) return SQLITE_NOMEM; pNew->pList = (Vec1TabList*)pAux; } return rc; } static int vec1catDisconnectMethod(sqlite3_vtab *pVtab){ vec1cat_vtab *p = (vec1cat_vtab *)pVtab; sqlite3_free(p); return SQLITE_OK; } static int vec1catOpenMethod(sqlite3_vtab *pVtab, sqlite3_vtab_cursor **ppCur){ vec1cat_cursor *pCur; UNUSED_PARAMETER(pVtab); pCur = vec1MallocZero(sizeof(*pCur)); if( pCur==0 ) return SQLITE_NOMEM; *ppCur = &pCur->base; return SQLITE_OK; } static int vec1catCloseMethod(sqlite3_vtab_cursor *cur){ vec1cat_cursor *pCur = (vec1cat_cursor *)cur; sqlite3_free(pCur); return SQLITE_OK; } static int vec1catBestIndexMethod( sqlite3_vtab *pVtab, sqlite3_index_info *pIdxInfo ){ UNUSED_PARAMETER(pVtab); pIdxInfo->estimatedCost = (double)10; pIdxInfo->estimatedRows = 10; return SQLITE_OK; } static int vec1catFilterMethod( sqlite3_vtab_cursor *cur, int idxNum, const char *idxStr, int argc, sqlite3_value **argv ){ Vec1TabList *pList = ((vec1cat_vtab*)(cur->pVtab))->pList; vec1cat_cursor *pCur = (vec1cat_cursor *)cur; sqlite3_stmt *pSql1 = 0; int rc = SQLITE_OK; const char *zSql1 = "SELECT name FROM pragma_database_list"; const char *zSql2 = "WITH tables(name) AS (" " SELECT name FROM %Q.sqlite_schema " " WHERE sql LIKE 'CREATE%%VIRTUAL%%USING%%vec1%%'" ")" "SELECT * FROM tables CROSS JOIN pragma_table_info(tables.name, %Q)"; UNUSED_PARAMETER2(argc, argv); UNUSED_PARAMETER2(idxStr, idxNum); rc = sqlite3_prepare_v2(pList->db, zSql1, -1, &pSql1, 0); if( rc==SQLITE_OK ){ while( sqlite3_step(pSql1)==SQLITE_ROW ){ const char *zDb = (const char*)sqlite3_column_text(pSql1, 0); sqlite3_stmt *pSql2 = 0; char *z = vec1MPrintf(&rc, zSql2, zDb, zDb); if( rc==SQLITE_OK ){ rc = sqlite3_prepare(pList->db, z, -1, &pSql2, 0); sqlite3_free(z); } if( rc==SQLITE_OK ){ while( sqlite3_step(pSql2)==SQLITE_ROW ); vec1StmtFinalize(&rc, pSql2); } } vec1StmtFinalize(&rc, pSql1); } pCur->iRowid = 1; return rc; } static int vec1catNextMethod(sqlite3_vtab_cursor *cur){ vec1cat_cursor *pCur = (vec1cat_cursor *)cur; pCur->iRowid++; return SQLITE_OK; } static Vec1Tab *vec1catGetTable(vec1cat_cursor *pCur){ Vec1TabList *pList = ((vec1cat_vtab*)(pCur->base.pVtab))->pList; Vec1Tab *pTab = pList->pFirst; int ii; for(ii=1; pTab && iiiRowid; ii++){ pTab = pTab->pTabNext; } return pTab; } static int vec1catEofMethod(sqlite3_vtab_cursor *cur){ return vec1catGetTable((vec1cat_cursor*)cur)==0; } /* ** xColumn() method for ephemeral virtual table vec1cat. */ static int vec1catColumnMethod( sqlite3_vtab_cursor *cur, sqlite3_context *ctx, int i ){ vec1cat_cursor *pCur = (vec1cat_cursor*)cur; Vec1Tab *pTab = vec1catGetTable(pCur); int rc = SQLITE_OK; switch( i ){ case 0: /* database */ sqlite3_result_text(ctx, pTab->zDb, -1, SQLITE_TRANSIENT); break; case 1: /* name */ sqlite3_result_text(ctx, pTab->zName, -1, SQLITE_TRANSIENT); break; case 2: { /* model */ Vec1ModelHeader *pHdr = &pTab->mod.hdr; const char *zDist = 0; char *zRet = 0; rc = vec1LoadConfig(pTab); zDist = ((pHdr->eDistance==VEC1_DISTANCE_L2) ? "l2" : "cos"); if( pHdr->nCodebook==0 && pHdr->nBucket==0 ){ zRet = vec1MPrintf(&rc, "{\"index\": \"%s\", \"distance\": \"%s\"}", ((pHdr->flags & VEC1_MODEL_INDEX) ? "flat" : "none"), zDist ); }else{ const char *zQuant = "none"; if( pHdr->nCodebook ){ if( pHdr->flags & VEC1_MODEL_BITQUANT ){ zQuant = "bq"; }else if( pHdr->flags & VEC1_MODEL_ROTATE ){ zQuant = "opq"; }else{ zQuant = "pq"; } } zRet = vec1MPrintf(&rc, "{" "\"index\": \"trained\", " "\"distance\": \"%s\", " "\"nbucket\": %d, " "\"quantizer\": \"%s\", " "\"codesize\": %d, " "\"residual\": %s, " "\"wht\": %s}", zDist, pHdr->nBucket, zQuant, pHdr->nCodebook, (pHdr->flags & VEC1_MODEL_RESIDUAL) ? "true" : "false", (pHdr->flags & VEC1_MODEL_WHT) ? "true" : "false" ); } if( rc==SQLITE_OK ){ sqlite3_result_text(ctx, zRet, -1, vec1SqliteFree); }else{ sqlite3_result_error_code(ctx, rc); } break; } default: { /* qprofile */ const char *azName[VEC1_QINSTR_NCOUNTER] = { "total", /* VEC1_QINSTR_TOTAL */ "init", /* VEC1_QINSTR_INITQUERY */ "coarse", /* VEC1_QINSTR_COARSE */ "idxread", /* VEC1_QINSTR_IDXREAD */ "metaread", /* VEC1_QINSTR_METAREAD */ "metascan", /* VEC1_QINSTR_METASCAN */ "lut", /* VEC1_QINSTR_LUT */ "pqscan", /* VEC1_QINSTR_PQSCAN */ "finalsort" /* VEC1_QINSTR_FINALSORT */ }; char *zRet = 0; int ii; for(ii=0; iiaCount[ii], pTab->aCycle[ii] ); } zRet = vec1MPrintf(&rc, "{%z\n}", zRet); if( rc==SQLITE_OK ){ sqlite3_result_text(ctx, zRet, -1, vec1SqliteFree); }else{ sqlite3_result_error_code(ctx, rc); } } } return SQLITE_OK; } static int vec1catRowidMethod( sqlite3_vtab_cursor *cur, sqlite3_int64 *pRowid ){ vec1cat_cursor *pCur = (vec1cat_cursor *)cur; *pRowid = pCur->iRowid; return SQLITE_OK; } /* ** End of vec1_cat virtual table. *************************************************************************/ /* ** Used to get/set per-connection transient parameters. Syntax: ** ** SELECT vec1_config(PARAM) ** SELECT vec1_config(PARAM, VALUE) ** ** There are currently two parameters: ** ** nthread: ** Default number of threads to use for training and rebuilding. ** ** nprobe: ** Default nprobe value for queries that do not specify one. ** */ static void vec1ConfigFunc( sqlite3_context *pCtx, int nVal, sqlite3_value **aVal ){ Vec1TabList *pList = (Vec1TabList*)sqlite3_user_data(pCtx); const char *zParam = (const char*)sqlite3_value_text(aVal[0]); if( 0==sqlite3_stricmp(zParam, "nthread") ){ if( nVal==2 ){ int nNew = sqlite3_value_int(aVal[1]); if( nNew<1 || nNew>VEC1_MAX_NTHREAD ){ vec1ResultErrorF(pCtx, "vec1: " "nthread requires an integer value between 1 and %d", VEC1_MAX_NTHREAD ); return; } #if VEC1_THREADS pList->nThread = nNew; #endif } sqlite3_result_int(pCtx, pList->nThread); }else if( 0==sqlite3_stricmp(zParam, "nprobe") ){ if( nVal==2 ){ int eNumeric = sqlite3_value_numeric_type(aVal[1]); double fNew = 0.0; if( eNumeric==SQLITE_INTEGER || eNumeric==SQLITE_FLOAT ){ fNew = sqlite3_value_double(aVal[1]); } if( fNew<=0.0 ){ vec1ResultErrorF(pCtx, "vec1: nprobe must be a numeric value greater than 0.0" ); return; } pList->nProbeArg = fNew; } sqlite3_result_double(pCtx, pList->nProbeArg); }else{ vec1ResultErrorF(pCtx, "vec1: no such parameter: %s", zParam); } } /* ** Destructor for the vec1 module registered using sqlite3_create_module_v2(). ** Free the Vec1TabList structure. SQLite should have ensured any virtual ** tables are closed before this is called. */ static void vec1ListFree(void *pCtx){ Vec1TabList *pList = (Vec1TabList*)pCtx; pList->nRef--; if( pList->nRef==0 ){ assert( pList->pFirst==0 ); sqlite3_free(pList); } } static int initExtension( sqlite3 *db, char **pzErr, const sqlite3_api_routines *pApi ){ static sqlite3_module vec1catModule = { 0, /* iVersion */ 0, /* xCreate */ vec1catConnectMethod, /* xConnect */ vec1catBestIndexMethod, /* xBestIndex */ vec1catDisconnectMethod, /* xDisconnect */ 0, /* xDestroy */ vec1catOpenMethod, /* xOpen */ vec1catCloseMethod, /* xClose */ vec1catFilterMethod, /* xFilter */ vec1catNextMethod, /* xNext */ vec1catEofMethod, /* xEof */ vec1catColumnMethod, /* xColumn */ vec1catRowidMethod, /* xRowid */ 0, /* xUpdate */ 0, /* xBegin */ 0, /* xSync */ 0, /* xCommit */ 0, /* xRollback */ 0, /* xFindMethod */ 0, /* xRename */ 0, /* xSavepoint */ 0, /* xRelease */ 0, /* xRollbackTo */ 0, /* xShadowName */ 0, /* xIntegrity */ }; static sqlite3_module vec1Module = { 4, /* iVersion */ vec1CreateMethod, /* xCreate */ vec1ConnectMethod, /* xConnect */ vec1BestIndexMethod, /* xBestIndex */ vec1DisconnectMethod, /* xDisconnect */ vec1DestroyMethod, /* xDestroy */ vec1Open, /* xOpen */ vec1Close, /* xClose */ vec1FilterMethod, /* xFilter */ vec1NextMethod, /* xNext */ vec1EofMethod, /* xEof */ vec1ColumnMethod, /* xColumn */ vec1Rowid, /* xRowid */ vec1UpdateMethod, /* xUpdate */ vec1BeginMethod, /* xBegin */ vec1SyncMethod, /* xSync */ 0, /* xCommit */ vec1RollbackMethod, /* xRollback */ 0, /* xFindFunction */ vec1RenameMethod, /* xRename */ vec1SavepointMethod, /* xSavepoint */ vec1ReleaseMethod, /* xRelease */ vec1RollbackToMethod, /* xRollbackTo */ 0, /* xShadowName */ vec1IntegrityMethod /* xIntegrity */ }; static struct Func { const char *zName; int nArg; void (*x)(sqlite3_context*,int,sqlite3_value**); } aFunc[] = { { "vec1_info", 0, vec1InfoFunc }, { "vec1_to_json", 1, vec1ToJsonFFunc }, { "vec1_from_json", 1, vec1FromJsonFunc }, { "vec1_l2_distance", 2, vec1DistanceFuncL2 }, { "vec1_cos_distance", 2, vec1DistanceFuncCos }, { "vec1_config", 1, vec1ConfigFunc }, { "vec1_config", 2, vec1ConfigFunc }, /* Currently undocumented functions. These may become documented when ** datatypes other than 32-bit float are supported for vectors */ { "vec1_to_json_f", 1, vec1ToJsonFFunc }, { "vec1_to_json_i", 1, vec1ToJsonIFunc }, }; static struct AggFunc { const char *zName; int nArg; void (*xStep)(sqlite3_context*,int,sqlite3_value**); void (*xFinal)(sqlite3_context*); } aAgg[] = { { "vec1_train", 1, vec1TrainStep, vec1TrainFinal }, { "vec1_train", 2, vec1TrainStep, vec1TrainFinal }, }; int ii = 0; int rc = SQLITE_OK; Vec1TabList *pList = vec1MallocZero(sizeof(Vec1TabList)); UNUSED_PARAMETER2(pApi, pzErr); /* Assuming the Vec1TabList object was allocated without error, populate ** it and create the main virtual table module - vec1. */ if( pList==0 ){ rc = SQLITE_NOMEM; }else{ pList->nThread = 1; pList->db = db; pList->nProbeArg = VEC1_QUERY_DEFAULT_NPROBE; pList->nRef = 1; rc = sqlite3_create_module_v2(db, "vec1", &vec1Module, pList, vec1ListFree); } /* Create the other vtab module - vec1cat */ if( rc==SQLITE_OK ){ pList->nRef++; rc = sqlite3_create_module_v2( db, "vec1cat", &vec1catModule, pList, vec1ListFree ); } /* Create the scalar functions */ for(ii=0; rc==SQLITE_OK && iizName, p->nArg, SQLITE_UTF8|SQLITE_DIRECTONLY, (void*)pList, 0, p->xStep, p->xFinal ); } /* If an error occurred, unregister everything */ if( rc!=SQLITE_OK ){ sqlite3_create_module(db, "vec1", 0, 0); sqlite3_create_module(db, "vec1cat", 0, 0); for(ii=0; ii static int vec1HasAvx2(void) { int regs[4]; __cpuid(regs, 1); int ecx = regs[2]; if (!(ecx & (1 << 28))) return 0; // AVX if (!(ecx & (1 << 27))) return 0; // OSXSAVE unsigned long long xcr0 = _xgetbv(0); if ((xcr0 & 0x6) != 0x6) return 0; __cpuidex(regs, 7, 0); int ebx = regs[1]; return (ebx & (1 << 5)) != 0; // AVX2 } # else static int vec1HasAvx2(){ /* Clang/GCC */ __builtin_cpu_init(); return __builtin_cpu_supports("avx2"); } # endif static int initMultiExtension( sqlite3 *db, char **pzErr, const sqlite3_api_routines *pApi ){ int rc = SQLITE_OK; if( vec1HasAvx2() ){ /* Use AVX2 */ rc = sqlite3Vec1InitAVX2(db, pzErr, pApi); }else{ /* Fall back to scalar. */ rc = initExtension(db, pzErr, pApi); } return rc; } # define initExtension initMultiExtension # endif /* VEC1SIMD==SCALAR */ #endif /* defined(VEC1SIMD) */ #if !defined(VEC1SIMD) || VEC1SIMD==SCALAR #ifndef VEC1_STATIC SQLITE_EXTENSION_INIT1; #endif #ifdef _WIN32 __declspec(dllexport) #endif int sqlite3_extension_init( sqlite3 *db, char **pzErrMsg, const sqlite3_api_routines *pApi ){ #ifdef SQLITE_EXTENSION_INIT2 SQLITE_EXTENSION_INIT2(pApi); #endif return initExtension(db, pzErrMsg, pApi); } #ifdef VEC1_STATIC int sqlite3_vec1_extra_init(const char *z){ sqlite3_auto_extension((void(*)(void))initExtension); return SQLITE_OK; } #endif #endif /* !defined(VEC1SIMD) || VEC1SIMD==SCALAR */