象使いのための pgvector 入門 (3) 0.6.0 〜 0.7.0 追加機能について

[2023 年末の Advent Calendar](https://qiita.com/advent-calendar/2023/postgresql) の時期に、

https://qiita.com/hmatsu47/items/b393cecef8ed9df57c35

https://qiita.com/hmatsu47/items/d805ae2b0fdc2a0cbd5f

の 2 本の記事を書きましたが、そこから半年が経過する間に追加された機能があるので、まとめてみました。

:::note
といいつつ中身はほぼ [README](https://github.com/pgvector/pgvector?tab=readme-ov-file) からの抜粋です。
:::

## 0.6.0 の追加機能

### パラレル処理による HNSW インデックス作成の高速化

[README の **Index Build Time**](https://github.com/pgvector/pgvector?tab=readme-ov-file#index-build-time) にあるように、CPU コアの数が多い環境で、処理の並列化により HNSW インデックス作成を高速化できるようになりました。

`SET max_parallel_maintenance_workers = 【並列処理に使うワーカー数（デフォルト：2）】;`で並列数を指定することができます。

:::note
`max_parallel_maintenance_workers`の数を増やすときは、パラレルワーカー自体の数（`max_parallel_workers`：デフォルトは`8`）の数も調整する必要があるかも知れません。

:::

[Supabase のブログ記事](https://supabase.com/blog/pgvector-fast-builds) に 0.5.x との比較がまとめられています。

https://supabase.com/blog/pgvector-fast-builds

## 0.7.0 の追加機能

主に、ベクトル計算やインデックス作成の負荷低減に有効な機能が追加されています。

こちらも、[Supabase のブログ記事](https://supabase.com/blog/pgvector-0-7-0)にまとめられています。

https://supabase.com/blog/pgvector-0-7-0

### 半精度ベクトル・半精度インデックス

これまでは 32 ビット（4 バイト）浮動小数点数によるベクトルデータをサポートしていましたが、[16 ビット（2 バイト）浮動小数点数によるベクトルデータ](https://github.com/pgvector/pgvector?tab=readme-ov-file#half-precision-vectors)および[インデックス](https://github.com/pgvector/pgvector?tab=readme-ov-file#half-precision-indexing)もサポートしました。

```sql:READMEの例①テーブル定義
CREATE TABLE items (id bigserial PRIMARY KEY, embedding halfvec(3));
```

```sql:READMEの例②インデックス
CREATE INDEX ON items USING hnsw ((embedding::halfvec(3)) halfvec_l2_ops);
```

:::note
次元あたりの容量が半減した分、インデックス作成可能な次元数が倍増しました（最大 4,000 次元）。
:::

### バイナリ（ビット）ベクトルおよびバイナリ量子化・バイナリインデックス

半精度だけではなく[バイナリ（ビット）ベクトル](https://github.com/pgvector/pgvector?tab=readme-ov-file#half-precision-indexing)および[バイナリ量子化・バイナリインデックス](https://github.com/pgvector/pgvector?tab=readme-ov-file#binary-quantization)もサポートしました。

```sql:READMEの例①テーブル定義とデータ投入
CREATE TABLE items (id bigserial PRIMARY KEY, embedding bit(3));
INSERT INTO items (embedding) VALUES ('000'), ('111');
```

2 進表現の文字列でデータを投入するようです。

```sql:READMEの例②近傍探索
SELECT * FROM items ORDER BY embedding <~> '101' LIMIT 5;
```

近傍探索するときは、ハミング距離（`<~>`）またはジャッカード距離（ジャッカード類似度・`<%>`）を使います。

```sql:READMEの例③バイナリ量子化によるインデックス作成
CREATE INDEX ON items USING hnsw ((binary_quantize(embedding)::bit(3)) bit_hamming_ops);
```

こちらはハミング距離用のインデックス作成例です。

:::note
次元あたりの容量が減少した分、インデックス作成可能な次元数が増加しました（最大 64,000 次元）。

また、[Cohere Embed](https://docs.cohere.com/docs/embed-api#whats-the-purpose-of-the-input-type) のような、API でバイナリベクトルの出力をサポートしている埋め込みモデルを使わなくても、このバイナリ量子化関数を使って 32 ビットのベクトルからバイナリベクトルを生成することができます。
:::

```sql:READMEの例④バイナリ量子化を使った Re-rank
SELECT * FROM (
    SELECT * FROM items ORDER BY binary_quantize(embedding)::bit(3) <~> binary_quantize('[1,-2,3]') LIMIT 20
) ORDER BY embedding <=> '[1,-2,3]' LIMIT 5;
```

最初にバイナリ量子化ベクトル（インデックス）による近傍探索によって 20 件の候補に絞り込んでおき、その中から 32 ビットのベクトルによって上位 5 件に絞り込むやり方です。

一定の精度は確保しつつ、ベクトルの距離比較の計算負荷を下げる手法ですね。

---

**2024/5/24 追記：**
Zenn に[追加の記事](https://zenn.dev/hmatsu47/articles/pgvector070-binaryvector)を書きました。

https://zenn.dev/hmatsu47/articles/pgvector070-binaryvector

---

### 疎ベクトル

0 が多く含まれるベクトルを扱う際に、[疎ベクトル](https://github.com/pgvector/pgvector?tab=readme-ov-file#sparse-vectors)の機能（表現）を使うことで記憶容量の削減が可能になりました。

[前述の Supabase のブログ記事](https://supabase.com/blog/pgvector-0-7-0#sparse-vectors)を参照してください。

```sql:READMEの例：テーブル定義とデータ投入
CREATE TABLE items (id bigserial PRIMARY KEY, embedding sparsevec(5));
INSERT INTO items (embedding) VALUES ('{1:1,3:2,5:3}/5'), ('{1:4,3:5,5:6}/5');
```

### サブベクトルのインデックス化

元のベクトルからサブベクトルを抽出してインデックスを作成することで、インデックス作成および検索時の計算負荷を低減できるようになりました。

```sql:READMEの例①サブベクトルインデックス（HNSW）作成
CREATE INDEX ON items USING hnsw ((subvector(embedding, 1, 3)::vector(3)) vector_cosine_ops);
```

```sql:READMEの例②サブベクトルインデックスを使った近傍探索
SELECT * FROM items ORDER BY subvector(embedding, 1, 3)::vector(3) <=> subvector('[1,2,3,4,5]'::vector, 1, 3) LIMIT 5;
```

```sql:READMEの例③サブベクトルインデックスを使った Re-rank
SELECT * FROM (
    SELECT * FROM items ORDER BY subvector(embedding, 1, 3)::vector(3) <=> subvector('[1,2,3,4,5]'::vector, 1, 3) LIMIT 20
) ORDER BY embedding <=> '[1,2,3,4,5]' LIMIT 5;
```

バイナリインデックスを使った Re-rank と同様のことができます。

### その他

L1 距離（マンハッタン距離・`<+>`）による比較もサポートされました。

:::note
ベクトルデータの精度（タイプ）別に使用可能な関数などが [README の **Reference**](https://github.com/pgvector/pgvector?tab=readme-ov-file#reference) にまとめられています。
:::

---

**2024/8/18 追記：**

pgvector による HNSW インデックス、およびバイナリ量子化の弱点を補う目的で登場した [pgvectorscale](https://github.com/timescale/pgvectorscale) を（少し）試してみた記事はこちら ↓ です。

https://zenn.dev/hmatsu47/articles/pgvectorscale

試してみた内容にはあまり意味はありませんが、

- 記事末尾の（2024/8/16 以降の）追記
- 当該追記からリンクしている Timescale 社のブログ記事

に、pgvector（0.7.0 時点）の問題点と pgvectorscale における改善アプローチが記されていますので、よかったらご覧ください。