SQLite向け新ベクター検索拡張 `sqlite-vec` を開発中
(alexgarcia.xyz)- 既存の
sqlite-vssの限界を減らすため、sqlite-vecは純粋なCベースの組み込み型ベクター検索拡張として開発中で、SQLiteが動作する幅広い環境を対象としている - SQLの利用フローは
CREATE VIRTUAL TABLE、INSERT INTO、SELECTを中心に単純化され、KNNスタイル検索とJSON・compact binaryのベクター入力をサポートする - Faiss依存をなくし、Linux、macOSだけでなくWindows、WebAssembly、モバイル、Raspberry Piまでを視野に入れ、バイナリサイズも従来の3MB〜5MBより小さい数百KB規模を見込む
- ベクターをshadow tableの**チャンク(chunk)**単位で保存し、全体をRAMに載せる必要を減らし、
PRAGMA mmap_sizeでメモリベース検索の速度を高められる - 初期バージョンはexhaustive full-scanのみを提供し、ANNは含まれず、
sqlite-vec.cの246個のTODOが完了した後にv0.1.0のリリースが予定されている
sqlite-vec が変えようとしているSQLiteベクター検索
sqlite-vecは純粋なCで書かれる新しいSQLite拡張であり、2023年2月に公開されたsqlite-vssを置き換えることを目指すプロジェクトである- 目標範囲には、高速なベクター検索のためのユーザー定義SQL関数、仮想テーブル(virtual table)、ベクター操作用のツールやユーティリティまで含まれる
- 量子化
- JSON/BLOB/numpy変換
- ベクター演算
- ユーザーは純粋なSQLだけでベクターストアを作成し、検索できる
CREATE VIRTUAL TABLEでベクター用の仮想テーブルを作成INSERT INTOでベクターを挿入SELECT ... WHERE sample_embedding MATCH ... ORDER BY distance LIMIT ...形式でKNNスタイル検索
- ベクター入力にはJSON文字列またはcompact binary形式を使用できる
純粋なCと無依存が広げる実行環境
sqlite-vecは無依存の純粋C拡張を目標としており、この選択が対応プラットフォームを広げるための重要な条件となっている- 既存の
sqlite-vssはC++依存のためLinuxとmacOSでしか安定動作せず、バイナリサイズは3MB〜5MBの範囲だった - 新しい拡張は次の環境での動作を目標としている
- Linux
- macOS
- Windows
- ブラウザ上のWebAssembly
- モバイル端末
- Raspberry Piのような小型デバイス
- 想定されるバイナリサイズは数百KBの範囲である
メモリ使用量と検索速度の調整
sqlite-vecはベクターをshadow table内のチャンクに分けて保存する方式でメモリ使用量を制御する- KNN検索時にすべてのベクターを一度にRAMへ載せず、チャンク単位で読み込む
- 全ベクターをメモリ常駐させる必要がない
- メモリベースの速度が必要なら、SQLiteの
PRAGMA mmap_sizeを使ってKNN検索をさらに高速化できる
新しいベクター機能と初期の制限
sqlite-vecは最近のベクター検索ツールや研究動向を踏まえ、次の機能をよりよくサポートしようとしている- 可変長埋め込み、つまり Matryoshka embeddings
int8およびbitベクター- binary and scalar quantization
- これらの機能は、ベクターの速度、精度、ディスク使用量をより細かく調整するための基盤になる
- 初期の
sqlite-vecは exhaustive full-scan ベクター検索のみをサポートする- “approximate nearest neighbors” オプションは当初はない
- IVFとHNSWは今後追加したい機能である
ブラウザデモの構成
- デモではブラウザで動作する
sqlite-vecを使用している - 開発者ツールで確認できる構成は次のとおり
- 最適化されていない 5.9MB の
sqlite3.wasm sqlite-vecがコンパイルされた 公式SQLite WASMビルド- 2.6MB サイズの
movies.bit.dbSQLiteデータベース
- 最適化されていない 5.9MB の
movies.bit.dbには TMDB映画メタデータ に基づく4,800本の映画概要がarticlesテーブルに格納されている- 別の
vec_movies仮想テーブルは、その概要埋め込みのベクターインデックスである- 埋め込みには Nomic 1.5 embeddings model を使用
- ベクターはbinary vectorに量子化されている
デモデータとKNN検索の流れ
articlesテーブルにはtitle、release_date、overviewのようなカラムがあるoverviewカラムには映画のあらすじを短い文章で収めており、デモでの埋め込み対象となっているvec_movies仮想テーブルはarticles.overviewの埋め込みをoverview_embeddingsカラムに保存する- ベクターは768次元のbinary vector
- 保存サイズは
768 / 8 = 96で96バイト
- ユーザーがラジオボタンで映画を選ぶと、選択した映画IDがKNN SQLクエリの
:selected_movieパラメータに入る - 検索結果は、選択した映画に最も近い10本の映画である
- binary vectorなので、距離計算にはhamming distanceを使う
- 最も近い結果は常に同じ映画で、距離は0である
- 短い単文のあらすじと小さな映画データセットを埋め込んだ結果は最高品質ではなく、binary quantizationによって品質はさらに犠牲になるが、ブラウザ内で高速かつ「十分に良い」ベクター検索を示すことに重点が置かれている
- 内部動作を確認するには
SELECTの前にEXPLAIN QUERY PLANを付ければよく、vec_moviesが使う0:knn“index” を見ることができる
sqlite-vss の限界とFaiss依存
sqlite-vssの開発と採用には複数の障害があった- LinuxとmacOSでしか動作せず、Windows、WASM、モバイル端末などはサポートできない
- ベクターをすべてメモリ内に保存する
- トランザクション関連のバグや問題がある
- コンパイルが非常に難しく、時間もかかる
- scalar/binary quantizationのような一般的なベクター操作が欠けている
- これらの問題のほとんどは
Faiss依存に起因している - 一部の問題は多くの時間と労力をかければ解決できるかもしれないが、かなりの部分はFaissが原因で行き詰まる可能性がある
- 無依存の低レベルソリューションが魅力的な選択肢となり、ベクター検索そのものもそれほど複雑ではないという判断から
sqlite-vecは始まった
リリース状況とスポンサー募集
sqlite-vecの中核機能は動作しているが、エラー処理とテストはまだ非常に不足しているsqlite-vec.cファイルには246個のTODOが残っているtodo_assert()191個// TODOコメント 41個todo panic14個- 全体進捗は 0/246、0%と表示されている
- 246個のTODOが完了すれば、最初の
v0.1.0リリースが公開される予定である- ドキュメント
- デモ
- バインディング
- その他の構成要素があわせて提供される予定
- 目標時期は約1か月程度だが、確定したスケジュールではない
sqlite-vecの成功に関心を持つ企業からのスポンサーを募集しており、メールで問い合わせできる
1件のコメント
Hacker News のコメント
作者です — 質問があれば答えます。これは正式リリースというより「新しいプロジェクトに取り組んでいる」という性格のもので、拡張機能自体もまだ作業中です。プロジェクトのリンクはこちらです: https://github.com/asg017/sqlite-vec
この拡張の v0.1.0 がどんな形になるかはかなり具体的に見えていますが、そこまではあと数週間かかりそうです。今回の記事は、以前作った SQLite ベクトル検索拡張である sqlite-vss のユーザーに、次に何が来るのかを知らせる目的が大きく、準備ができたらもっと大きなリリースを行う予定です。
全体としては、簡単に埋め込めるベクトル検索の代替を持てることにとても期待しています。あらゆる OS、WASM、モバイル端末、Raspberry Pi などで動くのが特に良い点で、個人的には Beepy で小さなセマンティック検索アプリを動かしてみようとしているので楽しみです。
[0] https://beepy.sqfmi.com/
sqlite-vss と比較した性能も知りたいです。クエリ速度とメモリ使用量の両方について、プロファイリングの数値が気になります。
全体として本当に素晴らしく見えますし、この方向性は気に入っています。
最初は sqlite-vec が全件スキャンによるベクトル検索だけをサポートし、近似最近傍探索(ANN)のオプションはないものの、後で IVF と HNSW を追加したい、というアプローチは 1000% 正しいと思います。最初から過度に複雑にしない点が良いです。
オンデバイスのベクトル検索をリリースしたことがありますが、128ビットのバイナリベクトルとハミング距離の組み合わせでは、データベースが20万件以上あっても、カメラの各フレームごとに完全な総当たり距離検索を回せるほど十分に高速でした。低価格帯のスマートフォンでも 10fps 以上出ていて、良いスマートフォンでは非常に滑らかでした。総当たりで十分なケースは驚くほど多いです。
ただし HNSW のような ANN アルゴリズムを実装する際には、テーブルインデックスのパラダイムとして扱えると素晴らしいと思います。そうすれば総当たり検索から ANN への切り替えが、テーブルにインデックスを作るのと同じくらい単純になり、複数の ANN アルゴリズムやパラメータの実験も、インデックス作成パラメータを調整する形で可能になります。すでにその方向かもしれませんが、念のため触れておきます。
sqlite-httpvfs と一緒にビルドするのかも気になります。このプロジェクトと相性が良さそうです: https://github.com/phiresky/sql.js-httpvfs
共通の SQL ベクトル DSL のために、pgvector と構文互換にすることも検討したのか気になります。メリットよりデメリットの方がずっと小さいのではないかと思いますが、可能なのか知りたいです。
https://observablehq.com/@asg017/introducing-sqlite-loadable...
sqlite-vss はすでにいくつかのプロジェクトでうまく使っています。
「768次元のバイナリベクトルなので 96バイトを占める(768 / 8 = 96)」という部分が混乱します。ほとんどのベクトルストアが直面する問題である次元の呪いはまさにこういう部分で、インデックス化以前の問題だと思います。
おそらく 768次元 * 8バイト(f64)で 6144バイトを意味しているのではないかと思いました。通常は多少の損失を受け入れて f32 や f16、あるいはもっと小さい表現に削ります。
圧縮やトライ木に似た償却的な方法などで 768次元を 96バイトに収める方法があるなら、別の記事でもっと聞きたいです。各次元を 1ビットとして扱うということなら理解できますが、その場合でも検索品質についてはまだ気になる点があります。
nomic v1.5[0] や mixedbread の新しいモデル[1]のように、一部の埋め込みモデルはバイナリ量子化後も品質が維持されるよう特別に学習されています。すべてのモデルがそうというわけではないので、結果は変わり得ます。一般的には OpenAI の 3072次元の大規模埋め込みモデルのような非常に大きなベクトルでは、そのために特別に学習していなくても、ある程度は機能するようです。
[0] https://twitter.com/nomic_ai/status/1769837800793243687
[1] https://www.mixedbread.ai/blog/binary-mrl
データに FAISS インデックスを使い、積量子化(Product Quantization)を適用すると、バイナリ特徴量の場合は PQ768x1 のような方式でバイナリベクトルを試せますし、ベクトルのペアごとに4つの値のいずれかへ量子化する方式なども比較できます: https://github.com/facebookresearch/faiss/wiki/The-index-fac...
通常、ベクトルデータベースでは保存前にデータをより低次元の空間へ圧縮または射影するため、むしろこの状況は改善されます。
sqlite-vss のおかげで、RAG がどう動作するのかを学び、トイプロジェクトに実装できた。デバッグは少し難しかったが、きちんと合わせれば Ubuntu で問題なく動作し、今でも使っている
制限の多い依存関係なしに、より良い新バージョンを作るというのはうれしい
公開 SQLite API だけを使う予定なのか、それとも SQLite amalgamation に組み込む形を想定しているのか気になる
こうした機能には確かに関心があるが、Wasm ベースの Go バインディングで SQLite とは別にどう配布するかを考える必要がある。これまでは Wasm の「動的リンク」よりずっと単純なので、C コードをすべてまとめて配布してきた
またインクリメンタル BLOB 入出力に触れているが、すでに知っているとは思うものの、大きな BLOB はページの連結リストとして保存されるため、BLOB 入出力は決してランダムアクセスではない点に注意すべき
wazero SQLite バインディングは本当に気に入っている。実際に 1) sqlite-vec 用の CGO バインディングと、2) go-sqlite3 から直接使えるカスタムの WASI ビルド sqlite-vec を提供する予定。もともとは、そのリポジトリのビルドスクリプトを使って sqlite3.wasm ファイルを作るつもりだった。プロジェクト側で直接サポートしたいなら、sqlite-vec.c/h ファイルを go-sqlite3/sqlite3 に入れるだけでよさそう
インクリメンタル BLOB 入出力については苦労して学んだ。sqlite-vec のクエリ速度では明らかに制約要因になっている。チャンクサイズを比較的小さく、数 MB 程度に抑え、page_size を大きくするとバランスは悪くなかったが、特に page_size には副作用がある。PRAGMA mmap_size もページをメモリ上に保持してオーバーフロー参照を速くするようで大いに役立つが、当然ながらメモリ使用量はかなり増える。難しいバランスだ
DuckDB が今日「Vector Similarity Search in DuckDB」拡張を発表した
https://duckdb.org/2024/05/03/vector-similarity-search-vss.h...
DuckDB VSS を使えば、埋め込みを作って DuckDB 形式で保存し、CDN 内で SQL を実行する形で処理できそう
こういうスタイルのプロジェクトは好き。とても特定の問題を狙ったオープンソースプロジェクトなのが良い
TypeScript/Next.js/React エコシステムでも、技術的なニッチにすごく役立つ何かを作れないかと考え続けているが、まだひらめきはない
AI RAG アプリの https://github.com/rnadigital/agentcloud でエンドツーエンド自動化に Qdrant ベクトル DB を使ったので、後継作を作っていると聞いて期待している。いつ頃使える状態になるのか、クイックスタートガイドがあるのか気になる
ブログ執筆も手伝えそう
ドキュメント化されていない
sqlite-vecpip パッケージがあるので、Python の「Agent Backend」から直接呼び出したいなら、今でも試せるとは思うこれは「README 駆動開発」がどんなものかと想像していたものにかなり近い。作者がドキュメントから始めたのか気になる
ただし「20% の労力で 80% を作った」状態なので、残り 20% であるエラー処理、ファズテスト、正確性テストが時間の 80% を占めそう。それでも、現在の
sqlite-vssの状態についてすでに質問している人たちがいるので、この「作業中」のブログ記事でいくつかの疑問に答えられると思ったドキュメントから始めるというアイデアも気に入っている。特に SQLite 拡張では、SQL API がどう見えるか、つまりスカラー関数や仮想テーブルなどが本当に重要。ほとんどのコードを書く前に、sqlite-vec の SQL 部分がどうあるべきかはかなりスケッチしていた
[0] https://github.com/asg017/sqlite-vec/blob/main/sqlite-vec.c
数か月前に SQLite-vss に上げた GitHub issue への答えに近いもののように思える。厳密にはその issue への回答ではないけれど
https://github.com/asg017/sqlite-vss/issues/124
実はそのチケットを最初に読んだとき、「sqlite-vss をどうすればもっと良くできるか」というラビットホールに入り込み、最終的に「sqlite-vec を作るべきだ」に行き着いた。この道に進む手助けをしてくれてありがとう
sqlite-vec の組み込みバイナリ量子化を使えば、おおよそこんな感じでできる:
CREATE VIRTUAL TABLE vec_files USING vec0 ( contents_embedding bit[1536] );INSERT INTO vec_files(rowid,contents_embedding) VALUES ((1, vec_quantize_binary( /* 1536-dimension float vector here*/)))ブラウザ内で実行する場合、sqlite-vec がブラウザネイティブの IndexedDB にデータを永続化できるのか気になります。あるいは、この部分はユーザーが自分で処理する必要があるのでしょうか。
まだ考えていないという回答でも構いませんが、その方向性について考えを共有してもらえるとありがたいです。
IndexedDB が具体的にサポートされているかは確かではありませんが、localStorage/OPFS VFS は利用できます。
[0] https://sqlite.org/wasm/doc/trunk/persistence.md#kvvfs