mirror of
https://github.com/pgvector/pgvector.git
synced 2026-07-22 03:57:34 +08:00
Compare commits
14 Commits
v0.6.2
...
target-clo
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
191bef7e35 | ||
|
|
582e6ad821 | ||
|
|
d0028ae769 | ||
|
|
81dc35b62b | ||
|
|
c82d15acad | ||
|
|
ba18942fcf | ||
|
|
8e59455c3c | ||
|
|
bd50e3067d | ||
|
|
af9d4ad659 | ||
|
|
08abb63cbe | ||
|
|
06b8556a49 | ||
|
|
3f674c9994 | ||
|
|
31e41b3ba9 | ||
|
|
903a925662 |
14
Makefile
14
Makefile
@@ -10,21 +10,15 @@ TESTS = $(wildcard test/sql/*.sql)
|
||||
REGRESS = $(patsubst test/sql/%.sql,%,$(TESTS))
|
||||
REGRESS_OPTS = --inputdir=test --load-extension=$(EXTENSION)
|
||||
|
||||
OPTFLAGS = -march=native
|
||||
OPTFLAGS =
|
||||
|
||||
# Mac ARM doesn't support -march=native
|
||||
# Since runtime dispatch not supported
|
||||
ifeq ($(shell uname -s), Darwin)
|
||||
ifeq ($(shell uname -p), arm)
|
||||
# no difference with -march=armv8.5-a
|
||||
OPTFLAGS =
|
||||
ifeq ($(shell uname -m), x86_64)
|
||||
OPTFLAGS = -march=native
|
||||
endif
|
||||
endif
|
||||
|
||||
# PowerPC doesn't support -march=native
|
||||
ifneq ($(filter ppc64%, $(shell uname -m)), )
|
||||
OPTFLAGS =
|
||||
endif
|
||||
|
||||
# For auto-vectorization:
|
||||
# - GCC (needs -ftree-vectorize OR -O3) - https://gcc.gnu.org/projects/tree-ssa/vectorization.html
|
||||
# - Clang (could use pragma instead) - https://llvm.org/docs/Vectorizers.html
|
||||
|
||||
20
README.md
20
README.md
@@ -604,6 +604,18 @@ and query with:
|
||||
SELECT * FROM items ORDER BY embedding::vector(3) <-> '[3,1,2]' LIMIT 5;
|
||||
```
|
||||
|
||||
#### Are binary vectors supported?
|
||||
|
||||
You can store binary vectors and perform exact nearest neighbor search by Hamming distance in Postgres without an extension ([example](https://github.com/pgvector/pgvector-python/blob/master/examples/hash_image_search.py)).
|
||||
|
||||
```tsql
|
||||
CREATE TABLE items (id bigserial PRIMARY KEY, embedding bit(3));
|
||||
INSERT INTO items (embedding) VALUES (B'000'), (B'111');
|
||||
SELECT * FROM items ORDER BY bit_count(embedding # B'101') LIMIT 5;
|
||||
```
|
||||
|
||||
Indexing is not currently supported.
|
||||
|
||||
#### Do indexes need to fit into memory?
|
||||
|
||||
No, but like other index types, you’ll likely see better performance if they do. You can get the size of an index with:
|
||||
@@ -659,6 +671,8 @@ ALTER TABLE items ALTER COLUMN embedding SET STORAGE PLAIN;
|
||||
|
||||
Results are limited by the size of the dynamic candidate list (`hnsw.ef_search`). There may be even less results due to dead tuples or filtering conditions in the query. We recommend setting `hnsw.ef_search` to at least twice the `LIMIT` of the query. If you need more than 500 results, use an IVFFlat index instead.
|
||||
|
||||
Also, note that `NULL` vectors are not indexed (as well as zero vectors for cosine distance).
|
||||
|
||||
#### Why are there less results for a query after adding an IVFFlat index?
|
||||
|
||||
The index was likely created with too little data for the number of lists. Drop the index until the table has more data.
|
||||
@@ -669,11 +683,13 @@ DROP INDEX index_name;
|
||||
|
||||
Results can also be limited by the number of probes (`ivfflat.probes`).
|
||||
|
||||
Also, note that `NULL` vectors are not indexed (as well as zero vectors for cosine distance).
|
||||
|
||||
## Reference
|
||||
|
||||
### Vector Type
|
||||
|
||||
Each vector takes `4 * dimensions + 8` bytes of storage. Each element is a single precision floating-point number (like the `real` type in Postgres), and all elements must be finite (no `NaN`, `Infinity` or `-Infinity`). Vectors can have up to 16,000 dimensions.
|
||||
Each vector takes `4 * dimensions + 8` bytes of storage. Each element is a single-precision floating-point number (like the `real` type in Postgres), and all elements must be finite (no `NaN`, `Infinity` or `-Infinity`). Vectors can have up to 16,000 dimensions.
|
||||
|
||||
### Vector Operators
|
||||
|
||||
@@ -697,7 +713,7 @@ l1_distance(vector, vector) → double precision | taxicab distance | 0.5.0
|
||||
vector_dims(vector) → integer | number of dimensions |
|
||||
vector_norm(vector) → double precision | Euclidean norm |
|
||||
|
||||
### Aggregate Functions
|
||||
### Vector Aggregate Functions
|
||||
|
||||
Function | Description | Added
|
||||
--- | --- | ---
|
||||
|
||||
@@ -262,7 +262,6 @@ typedef struct HnswBuildState
|
||||
HnswGraph *graph;
|
||||
double ml;
|
||||
int maxLevel;
|
||||
Vector *normvec;
|
||||
|
||||
/* Memory */
|
||||
MemoryContext graphCtx;
|
||||
@@ -367,7 +366,7 @@ typedef struct HnswVacuumState
|
||||
int HnswGetM(Relation index);
|
||||
int HnswGetEfConstruction(Relation index);
|
||||
FmgrInfo *HnswOptionalProcInfo(Relation index, uint16 procnum);
|
||||
bool HnswNormValue(FmgrInfo *procinfo, Oid collation, Datum *value, Vector * result);
|
||||
bool HnswNormValue(FmgrInfo *procinfo, Oid collation, Datum *value);
|
||||
Buffer HnswNewBuffer(Relation index, ForkNumber forkNum);
|
||||
void HnswInitPage(Buffer buf, Page page);
|
||||
void HnswInit(void);
|
||||
|
||||
@@ -489,7 +489,7 @@ InsertTuple(Relation index, Datum *values, bool *isnull, ItemPointer heaptid, Hn
|
||||
/* Normalize if needed */
|
||||
if (buildstate->normprocinfo != NULL)
|
||||
{
|
||||
if (!HnswNormValue(buildstate->normprocinfo, buildstate->collation, &value, buildstate->normvec))
|
||||
if (!HnswNormValue(buildstate->normprocinfo, buildstate->collation, &value))
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -703,9 +703,6 @@ InitBuildState(HnswBuildState * buildstate, Relation heap, Relation index, Index
|
||||
buildstate->ml = HnswGetMl(buildstate->m);
|
||||
buildstate->maxLevel = HnswGetMaxLevel(buildstate->m);
|
||||
|
||||
/* Reuse for each tuple */
|
||||
buildstate->normvec = InitVector(buildstate->dimensions);
|
||||
|
||||
buildstate->graphCtx = GenerationContextCreate(CurrentMemoryContext,
|
||||
"Hnsw build graph context",
|
||||
#if PG_VERSION_NUM >= 150000
|
||||
@@ -729,7 +726,6 @@ InitBuildState(HnswBuildState * buildstate, Relation heap, Relation index, Index
|
||||
static void
|
||||
FreeBuildState(HnswBuildState * buildstate)
|
||||
{
|
||||
pfree(buildstate->normvec);
|
||||
MemoryContextDelete(buildstate->graphCtx);
|
||||
MemoryContextDelete(buildstate->tmpCtx);
|
||||
}
|
||||
|
||||
@@ -622,7 +622,7 @@ HnswInsertTuple(Relation index, Datum *values, bool *isnull, ItemPointer heap_ti
|
||||
normprocinfo = HnswOptionalProcInfo(index, HNSW_NORM_PROC);
|
||||
if (normprocinfo != NULL)
|
||||
{
|
||||
if (!HnswNormValue(normprocinfo, collation, &value, NULL))
|
||||
if (!HnswNormValue(normprocinfo, collation, &value))
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -84,7 +84,7 @@ GetScanValue(IndexScanDesc scan)
|
||||
|
||||
/* Fine if normalization fails */
|
||||
if (so->normprocinfo != NULL)
|
||||
HnswNormValue(so->normprocinfo, so->collation, &value, NULL);
|
||||
HnswNormValue(so->normprocinfo, so->collation, &value);
|
||||
}
|
||||
|
||||
return value;
|
||||
|
||||
@@ -158,16 +158,14 @@ HnswOptionalProcInfo(Relation index, uint16 procnum)
|
||||
* if it's different than the original value
|
||||
*/
|
||||
bool
|
||||
HnswNormValue(FmgrInfo *procinfo, Oid collation, Datum *value, Vector * result)
|
||||
HnswNormValue(FmgrInfo *procinfo, Oid collation, Datum *value)
|
||||
{
|
||||
double norm = DatumGetFloat8(FunctionCall1Coll(procinfo, collation, *value));
|
||||
|
||||
if (norm > 0)
|
||||
{
|
||||
Vector *v = DatumGetVector(*value);
|
||||
|
||||
if (result == NULL)
|
||||
result = InitVector(v->dim);
|
||||
Vector *result = InitVector(v->dim);
|
||||
|
||||
for (int i = 0; i < v->dim; i++)
|
||||
result->x[i] = v->x[i] / norm;
|
||||
|
||||
@@ -57,7 +57,7 @@ AddSample(Datum *values, IvfflatBuildState * buildstate)
|
||||
*/
|
||||
if (buildstate->kmeansnormprocinfo != NULL)
|
||||
{
|
||||
if (!IvfflatNormValue(buildstate->kmeansnormprocinfo, buildstate->collation, &value, buildstate->normvec))
|
||||
if (!IvfflatNormValue(buildstate->kmeansnormprocinfo, buildstate->collation, &value))
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -153,7 +153,7 @@ AddTupleToSort(Relation index, ItemPointer tid, Datum *values, IvfflatBuildState
|
||||
/* Normalize if needed */
|
||||
if (buildstate->normprocinfo != NULL)
|
||||
{
|
||||
if (!IvfflatNormValue(buildstate->normprocinfo, buildstate->collation, &value, buildstate->normvec))
|
||||
if (!IvfflatNormValue(buildstate->normprocinfo, buildstate->collation, &value))
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -356,9 +356,6 @@ InitBuildState(IvfflatBuildState * buildstate, Relation heap, Relation index, In
|
||||
buildstate->centers = VectorArrayInit(buildstate->lists, buildstate->dimensions);
|
||||
buildstate->listInfo = palloc(sizeof(ListInfo) * buildstate->lists);
|
||||
|
||||
/* Reuse for each tuple */
|
||||
buildstate->normvec = InitVector(buildstate->dimensions);
|
||||
|
||||
buildstate->tmpCtx = AllocSetContextCreate(CurrentMemoryContext,
|
||||
"Ivfflat build temporary context",
|
||||
ALLOCSET_DEFAULT_SIZES);
|
||||
@@ -380,7 +377,6 @@ FreeBuildState(IvfflatBuildState * buildstate)
|
||||
{
|
||||
VectorArrayFree(buildstate->centers);
|
||||
pfree(buildstate->listInfo);
|
||||
pfree(buildstate->normvec);
|
||||
|
||||
#ifdef IVFFLAT_KMEANS_DEBUG
|
||||
pfree(buildstate->listSums);
|
||||
|
||||
@@ -172,7 +172,6 @@ typedef struct IvfflatBuildState
|
||||
VectorArray samples;
|
||||
VectorArray centers;
|
||||
ListInfo *listInfo;
|
||||
Vector *normvec;
|
||||
|
||||
#ifdef IVFFLAT_KMEANS_DEBUG
|
||||
double inertia;
|
||||
@@ -267,7 +266,7 @@ void VectorArrayFree(VectorArray arr);
|
||||
void PrintVectorArray(char *msg, VectorArray arr);
|
||||
void IvfflatKmeans(Relation index, VectorArray samples, VectorArray centers);
|
||||
FmgrInfo *IvfflatOptionalProcInfo(Relation index, uint16 procnum);
|
||||
bool IvfflatNormValue(FmgrInfo *procinfo, Oid collation, Datum *value, Vector * result);
|
||||
bool IvfflatNormValue(FmgrInfo *procinfo, Oid collation, Datum *value);
|
||||
int IvfflatGetLists(Relation index);
|
||||
void IvfflatGetMetaPageInfo(Relation index, int *lists, int *dimensions);
|
||||
void IvfflatUpdateList(Relation index, ListInfo listInfo, BlockNumber insertPage, BlockNumber originalInsertPage, BlockNumber startPage, ForkNumber forkNum);
|
||||
|
||||
@@ -85,7 +85,7 @@ InsertTuple(Relation index, Datum *values, bool *isnull, ItemPointer heap_tid, R
|
||||
normprocinfo = IvfflatOptionalProcInfo(index, IVFFLAT_NORM_PROC);
|
||||
if (normprocinfo != NULL)
|
||||
{
|
||||
if (!IvfflatNormValue(normprocinfo, index->rd_indcollation[0], &value, NULL))
|
||||
if (!IvfflatNormValue(normprocinfo, index->rd_indcollation[0], &value))
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -293,7 +293,7 @@ ivfflatgettuple(IndexScanDesc scan, ScanDirection dir)
|
||||
|
||||
/* Fine if normalization fails */
|
||||
if (so->normprocinfo != NULL)
|
||||
IvfflatNormValue(so->normprocinfo, so->collation, &value, NULL);
|
||||
IvfflatNormValue(so->normprocinfo, so->collation, &value);
|
||||
}
|
||||
|
||||
IvfflatBench("GetScanLists", GetScanLists(scan, value));
|
||||
|
||||
@@ -75,16 +75,14 @@ IvfflatOptionalProcInfo(Relation index, uint16 procnum)
|
||||
* if it's different than the original value
|
||||
*/
|
||||
bool
|
||||
IvfflatNormValue(FmgrInfo *procinfo, Oid collation, Datum *value, Vector * result)
|
||||
IvfflatNormValue(FmgrInfo *procinfo, Oid collation, Datum *value)
|
||||
{
|
||||
double norm = DatumGetFloat8(FunctionCall1Coll(procinfo, collation, *value));
|
||||
|
||||
if (norm > 0)
|
||||
{
|
||||
Vector *v = DatumGetVector(*value);
|
||||
|
||||
if (result == NULL)
|
||||
result = InitVector(v->dim);
|
||||
Vector *result = InitVector(v->dim);
|
||||
|
||||
for (int i = 0; i < v->dim; i++)
|
||||
result->x[i] = v->x[i] / norm;
|
||||
|
||||
50
src/vector.c
50
src/vector.c
@@ -29,6 +29,15 @@
|
||||
#define STATE_DIMS(x) (ARR_DIMS(x)[0] - 1)
|
||||
#define CreateStateDatums(dim) palloc(sizeof(Datum) * (dim + 1))
|
||||
|
||||
#if defined(__x86_64__) && defined(__gnu_linux__) && defined(__has_attribute) && __has_attribute(target_clones)
|
||||
#define RUNTIME_DISPATCH __attribute__((target_clones("default", "avx", "fma", "avx512f")))
|
||||
#elif defined(__aarch64__) && defined(__gnu_linux__) && defined(__has_attribute) && __has_attribute(target_clones)
|
||||
/* TODO Fix error: target does not support function version dispatcher */
|
||||
#define RUNTIME_DISPATCH __attribute__((target_clones("default", "arch=armv8.5-a")))
|
||||
#else
|
||||
#define RUNTIME_DISPATCH
|
||||
#endif
|
||||
|
||||
PG_MODULE_MAGIC;
|
||||
|
||||
/*
|
||||
@@ -532,6 +541,23 @@ vector_to_float4(PG_FUNCTION_ARGS)
|
||||
PG_RETURN_POINTER(result);
|
||||
}
|
||||
|
||||
RUNTIME_DISPATCH
|
||||
static float
|
||||
l2_squared_distance_impl(int16 dim, float *ax, float *bx)
|
||||
{
|
||||
float distance = 0.0;
|
||||
|
||||
/* Auto-vectorized */
|
||||
for (int16 i = 0; i < dim; i++)
|
||||
{
|
||||
float diff = ax[i] - bx[i];
|
||||
|
||||
distance += diff * diff;
|
||||
}
|
||||
|
||||
return distance;
|
||||
}
|
||||
|
||||
/*
|
||||
* Get the L2 distance between vectors
|
||||
*/
|
||||
@@ -541,19 +567,11 @@ l2_distance(PG_FUNCTION_ARGS)
|
||||
{
|
||||
Vector *a = PG_GETARG_VECTOR_P(0);
|
||||
Vector *b = PG_GETARG_VECTOR_P(1);
|
||||
float *ax = a->x;
|
||||
float *bx = b->x;
|
||||
float distance = 0.0;
|
||||
float diff;
|
||||
float distance;
|
||||
|
||||
CheckDims(a, b);
|
||||
|
||||
/* Auto-vectorized */
|
||||
for (int i = 0; i < a->dim; i++)
|
||||
{
|
||||
diff = ax[i] - bx[i];
|
||||
distance += diff * diff;
|
||||
}
|
||||
distance = l2_squared_distance_impl(a->dim, a->x, b->x);
|
||||
|
||||
PG_RETURN_FLOAT8(sqrt((double) distance));
|
||||
}
|
||||
@@ -568,19 +586,11 @@ vector_l2_squared_distance(PG_FUNCTION_ARGS)
|
||||
{
|
||||
Vector *a = PG_GETARG_VECTOR_P(0);
|
||||
Vector *b = PG_GETARG_VECTOR_P(1);
|
||||
float *ax = a->x;
|
||||
float *bx = b->x;
|
||||
float distance = 0.0;
|
||||
float diff;
|
||||
float distance;
|
||||
|
||||
CheckDims(a, b);
|
||||
|
||||
/* Auto-vectorized */
|
||||
for (int i = 0; i < a->dim; i++)
|
||||
{
|
||||
diff = ax[i] - bx[i];
|
||||
distance += diff * diff;
|
||||
}
|
||||
distance = l2_squared_distance_impl(a->dim, a->x, b->x);
|
||||
|
||||
PG_RETURN_FLOAT8((double) distance);
|
||||
}
|
||||
|
||||
@@ -116,8 +116,30 @@ SELECT '[1, ,3]'::vector;
|
||||
ERROR: invalid input syntax for type vector: "[1, ,3]"
|
||||
LINE 1: SELECT '[1, ,3]'::vector;
|
||||
^
|
||||
SELECT '[1,2,3]'::vector(3);
|
||||
vector
|
||||
---------
|
||||
[1,2,3]
|
||||
(1 row)
|
||||
|
||||
SELECT '[1,2,3]'::vector(2);
|
||||
ERROR: expected 2 dimensions, not 3
|
||||
SELECT '[1,2,3]'::vector(3, 2);
|
||||
ERROR: invalid type modifier
|
||||
LINE 1: SELECT '[1,2,3]'::vector(3, 2);
|
||||
^
|
||||
SELECT '[1,2,3]'::vector('a');
|
||||
ERROR: invalid input syntax for type integer: "a"
|
||||
LINE 1: SELECT '[1,2,3]'::vector('a');
|
||||
^
|
||||
SELECT '[1,2,3]'::vector(0);
|
||||
ERROR: dimensions for type vector must be at least 1
|
||||
LINE 1: SELECT '[1,2,3]'::vector(0);
|
||||
^
|
||||
SELECT '[1,2,3]'::vector(16001);
|
||||
ERROR: dimensions for type vector cannot exceed 16000
|
||||
LINE 1: SELECT '[1,2,3]'::vector(16001);
|
||||
^
|
||||
SELECT unnest('{"[1,2,3]", "[4,5,6]"}'::vector[]);
|
||||
unnest
|
||||
---------
|
||||
|
||||
@@ -22,7 +22,13 @@ SELECT '[1,]'::vector;
|
||||
SELECT '[1a]'::vector;
|
||||
SELECT '[1,,3]'::vector;
|
||||
SELECT '[1, ,3]'::vector;
|
||||
|
||||
SELECT '[1,2,3]'::vector(3);
|
||||
SELECT '[1,2,3]'::vector(2);
|
||||
SELECT '[1,2,3]'::vector(3, 2);
|
||||
SELECT '[1,2,3]'::vector('a');
|
||||
SELECT '[1,2,3]'::vector(0);
|
||||
SELECT '[1,2,3]'::vector(16001);
|
||||
|
||||
SELECT unnest('{"[1,2,3]", "[4,5,6]"}'::vector[]);
|
||||
SELECT '{"[1,2,3]"}'::vector(2)[];
|
||||
|
||||
Reference in New Issue
Block a user