Skip to content

This is the multi-page printable view of this section. .

Return to the regular view of this page.

Vector Types

In the previous sections Indexing and Search, we encountered the vector type. This section will explore additional vector types and their applications in the re

In the previous sections Indexing and Search, we encountered the vector type. This section will explore additional vector types and their applications in the realm of vector search.

Low-precision Indexing

since v0.3.0

To improve the speed of queries on the vector column, consider creating a low-precision index using vecf16, veci8, or bvector types instead of a vector type.

CREATE TABLE t (val vector(3));
INSERT INTO t (val) SELECT ARRAY[random(), random(), random()]::real[] FROM generate_series(1, 1000);

-- Create bvector index at vector column
-- Warning: Float32 vectors are truncated to 
--          integers of either 0 or 1 during index construction
CREATE INDEX lp_bin ON t USING vectors ((binarize(val)::bvector(3)) bvector_l2_ops);

-- Create vecf16 index at vector column
CREATE INDEX lp_f16 ON t USING vectors ((val::vecf16(3)) vecf16_l2_ops);

-- Create veci8 index at vector column
-- Warning: Float32 vectors are truncated to 
--          integers ranging from -128 to 127 during index construction
CREATE INDEX lp_i8 ON t USING vectors ((val::veci8(3)) veci8_l2_ops);

Shortening Embedding

since v0.3.0

Certain embedding models like OpenAI text-embedding-3-small support shortening embeddings, which involves removing some numbers from the end of the sequence without losing the embedding’s concept-representing properties.

In some cases, it may be necessary to change the embedding dimension after generation. The pgvecto.rs offers an approach to query by shortened embeddings.

CREATE TABLE t (val vector(3));
INSERT INTO t (val) SELECT ARRAY[random(), random(), random()]::real[] FROM generate_series(1, 1000);
CREATE INDEX ON t USING vectors (val vector_l2_ops);

-- Measure distance using only the first two dimensions
SELECT * from t ORDER BY (val[:2])::vector(2) <-> '[3, 2]'::vector;

Shortened embeddings are supported by all data types in pgvecto.rs since version v0.3.0.

Casts

This diagram shows the conversion between different data types, where the types connected by arrows can be cast to each other:

Among them, ARRAY is a native type of postgreSQL, others are types defined by pgvecto.rs.

Here are some examples of vector type casting:

-- OK: String ⇒ vector
SELECT '{1,2,3}'::vector;

-- OK: vector ⇒ ARRAY
SELECT '{1,2,3}'::vector::real[];

-- OK: ARRAY ⇒ vector ⇒ bvector
SELECT ARRAY[1.0 ,0 ,0]::real[]::vector::bvector;

-- ERROR: cannot cast type vecf16 to veci8
SELECT '[3, 2]'::vecf16::veci8;

-- OK: vecf16 ⇒ vector ⇒ veci8
SELECT '[3, 2]'::vecf16::vector::veci8;

1 - Half-Precision Vector: `vecf16`

Stored as a half-precision number format, vecf16 takes advantage of the 16-bit float, which uses half the memory and bandwidth compared to vector.

Stored as a half-precision number format, vecf16 takes advantage of the 16-bit float, which uses half the memory and bandwidth compared to vector.

Here’s an example of creating a table with a vecf16 column and inserting values:

CREATE TABLE items (
  id bigserial PRIMARY KEY,
  embedding vecf16(3) NOT NULL
);

INSERT INTO items (embedding) VALUES ('[0.1, 0.2, 0]'), ('[0, 0.1, 0.2]');

Index can be created on vecf16 type as well.

CREATE INDEX your_index_name ON items USING vectors (embedding vecf16_l2_ops);

SELECT * FROM items ORDER BY embedding <-> '[0.3,0.2,0.1]' LIMIT 1;

We support three operators to calculate the distance between two vecf16 values.

  • <-> (vecf16_l2_ops): squared Euclidean distance, defined as Σ(xi−yi)2\Sigma (x_i - y_i) ^ 2.
  • <#> (vecf16_dot_ops): negative dot product, defined as −Σxiyi- \Sigma x_iy_i.
  • <=> (vecf16_cos_ops): cosine distance, defined as 1−ΣxiyiΣxi2Σyi21 - \frac{\Sigma x_iy_i}{\sqrt{\Sigma x_i^2 \Sigma y_i^2}}.

2 - 8-Bit Integer Vector: `veci8`

Int8 is a data type used to represent signed integers. It stands for “integer 8-bit” and typically uses 8 bits of memory to store a value. This allows it to rep
since v0.3.0

Int8 is a data type used to represent signed integers. It stands for “integer 8-bit” and typically uses 8 bits of memory to store a value. This allows it to represent integer values ranging from -128 to 127.

Since each element in the vector only requires 8 bits (1 byte) of memory, veci8 can help reduce the overall memory footprint of your program.

Here’s an example of creating a table with a veci8 column and inserting values:

CREATE TABLE items (
  id bigserial PRIMARY KEY,
  embedding veci8(3) NOT NULL
);

INSERT INTO items (embedding) VALUES ('[1, 2, 0]'), ('[-1, 15, -2]');

Index can be created on veci8 type as well.

CREATE INDEX your_index_name ON items USING vectors (embedding veci8_l2_ops);

SELECT * FROM items ORDER BY embedding <-> '[0.3,0.2,0.1]' LIMIT 1;

We support three operators to calculate the distance between two veci8 values.

  • <-> (veci8_l2_ops): squared Euclidean distance, defined as Σ(xi−yi)2\Sigma (x_i - y_i) ^ 2.
  • <#> (veci8_dot_ops): negative dot product, defined as −Σxiyi- \Sigma x_iy_i.
  • <=> (veci8_cos_ops): cosine distance, defined as 1−ΣxiyiΣxi2Σyi21 - \frac{\Sigma x_iy_i}{\sqrt{\Sigma x_i^2 \Sigma y_i^2}}.

3 - Sparse Vector: `svector`

Unlike dense vectors, sparse vectors are very high-dimensional but contain few non-zero values.
since v0.3.0

Unlike dense vectors, sparse vectors are very high-dimensional but contain few non-zero values.

Typically, sparse vectors can be created from:

  • Word co-occurrence matrices
  • Term frequency-inverse document frequency (TF-IDF) vectors
  • User-item interaction matrices
  • Network adjacency matrices

Sparse vectors in pgvecto.rs are called svector.

Here’s an example of creating a table with a svector column and inserting values:

CREATE TABLE items (
  id bigserial PRIMARY KEY,
  embedding svector(10) NOT NULL
);

INSERT INTO items (embedding) VALUES ('[0.1,0,0,0,0,0,0,0,0,0]'), ('[0,0,0,0,0,0,0,0,0,0.5]');

Index can be created on svector type as well.

CREATE INDEX your_index_name ON items USING vectors (embedding svector_l2_ops);

SELECT * FROM items ORDER BY embedding <-> '[0.3,0,0,0,0,0,0,0,0,0]' LIMIT 1;

We support three operators to calculate the distance between two svector values.

  • <-> (svector_l2_ops): squared Euclidean distance, defined as Σ(xi−yi)2\Sigma (x_i - y_i) ^ 2.
  • <#> (svector_dot_ops): negative dot product, defined as −Σxiyi- \Sigma x_iy_i.
  • <=> (svector_cos_ops): cosine distance, defined as 1−ΣxiyiΣxi2Σyi21 - \frac{\Sigma x_iy_i}{\sqrt{\Sigma x_i^2 \Sigma y_i^2}}.

There is also a function to_svector to create a svector. It will set the value at the specified position.

F(index)={value[v]∃v∈N:position[v]=index0else F(index)=\left\{ \begin{array}{rcl} value[v] & & {\exists v \in \mathbb{N}: position[v] = index }\\ 0 & & {else}\\ \end{array} \right.
-- to_svector(dim: INTEGER, position: ARRAY, value: ARRAY) -> svector
SELECT to_svector(5, '{0, 4}', '{0.3, 0.5}');
-- [0.3, 0, 0, 0, 0.5]

4 - Binary Vector: `bvector`

The bvector type is a binary vector type in pgvecto.rs. It represents a binary vector, which is a vector where each component can take on two possible values, t
since v0.3.0

The bvector type is a binary vector type in pgvecto.rs. It represents a binary vector, which is a vector where each component can take on two possible values, typically 0 and 1.

Here’s an example of creating a table with a bvector column and inserting values:

CREATE TABLE items (
  id bigserial PRIMARY KEY,
  embedding bvector(3) NOT NULL
);

INSERT INTO items (embedding) VALUES ('[1,0,1]'), ('[0,1,0]');

Index can be created on bvector type as well.

CREATE INDEX your_index_name ON items USING vectors (embedding bvector_l2_ops);

SELECT * FROM items ORDER BY embedding <-> '[1,0,1]' LIMIT 5;

We support three operators to calculate the distance between two bvector values.

  • <-> (bvector_l2_ops): squared Euclidean distance, defined as Σ(xi−yi)2\Sigma (x_i - y_i) ^ 2. The Hamming distance is equivalent to the squared Euclidean distance for binary vectors.
  • <#> (bvector_dot_ops): negative dot product, defined as −Σxiyi- \Sigma x_iy_i.
  • <=> (bvector_cos_ops): cosine distance, defined as 1−ΣxiyiΣxi2Σyi21 - \frac{\Sigma x_iy_i}{\sqrt{\Sigma x_i^2 \Sigma y_i^2}}.
  • <~> (bvector_jaccard_ops): Jaccard distance, defined as 1−∣X∩Y∣∣X∪Y∣1 - \frac{|X\cap Y|}{|X\cup Y|}.

There is also a function binarize to build bvector from vector. It truncates vector elements to integers of 0 or 1.

F(element)={0element<11element≥1 F(element)=\left\{ \begin{array}{rcl} 0 & & {element < 1}\\ 1 & & {element \geq 1}\\ \end{array} \right.
-- binarize(source: vector) -> bvector
SELECT binarize('[-2, -1, 0, 1, 2]'::vector);
-- [0, 0, 0, 1, 1]

Performance

The bvector type is optimized for storage and performance. It uses a bit-packed representation to store the binary vector. The distance calculation is also optimized for binary vectors.

Here are some performance benchmarks for the bvector type. We use the dbpedia-entities-openai3-text-embedding-3-large-3072-1M dataset for the benchmark. The VM is n2-standard-8 (8 vCPUs, 32 GB memory) on Google Cloud.

We upsert 1M binary vectors into the table and then run a KNN query for each embedding. It only takes about 600MB memory to index 1M binary vectors, while the vector type takes about 18GB memory to index the same number of vectors.

bvector

We can see that the bvector’s accuracy is not as good as the vector type, but it exceeds 95% if we adopt adaptive retrieval.