技術 約7分で読めます

Rust/WASM製VecLiteでブラウザ内RAGの検索部分が現実味を帯びた

いけさん目次

DEV Communityに I built a vector search library in Rust/WASM. Here’s what I learned about performance, browser limits, and building in public with AI という記事が出ていた。
作者のAakash T M氏が、ブラウザ内で動くベクトル検索ライブラリ VecLite を作った話だ。

狙いはprivacy-firstなRAG。
文書をブラウザから外に出さず、Transformers.js でクライアント側Embeddingを作り、ベクトルをローカル保存し、検索もブラウザ内で済ませる。
APIキーなし、サーバーなし、外部のベクトルDBなし、という方向。

このブログでは最近、open-notebookをOllamaだけでローカルRAGとして動かしたし、MintlifyがRAGを捨ててChromaFsに切り替えた話も書いた。
どちらも「RAGをどう運用するか」の話だったが、VecLiteはもっと手前の部品、つまり「ブラウザだけでベクトル検索をどこまでやれるか」の話になっている。

サーバー側ベクトルDBの手前に空いていた穴

RAGの典型構成は、文書をチャンクに分けてEmbeddingにし、Chroma、Pinecone、pgvector、OpenSearchのような検索基盤に入れる。

ただし、個人のノート、ブラウザ拡張、Electronアプリ、オフライン検索のような用途だと、サーバー側ベクトルDBは重い。
ユーザーのローカル文書を検索したいだけなのに、検索のためにサーバーへ送るならprivacy-firstではなくなる。

VecLiteが狙っているのはこの隙間だ。

flowchart TD
  A[ブラウザ内の文書] --> B[Transformers.js等で<br/>Embedding生成]
  B --> C[VecLite<br/>Rust/WASM検索]
  C --> D[IndexedDB等に<br/>ローカル保存]
  C --> E[検索結果を<br/>RAG文脈へ渡す]

大きなベクトルDBを置き換えるというより、「1ユーザーの端末内で完結する検索インデックス」を作る位置づけに見える。
AliSQLのHNSWベクトルサーチ のようなDB統合型とは、かなり違うレイヤーの話だ。

JavaScriptの限界は計算式よりメモリとGCで来る

コサイン類似度そのものは難しい計算ではない。
ベクトルと行列の記事でも触れたように、Embedding検索はざっくり言えば「向きの近さ」を大量に計算している。

問題は回数だ。
原典では、10,000件のベクトル、1,536次元のEmbeddingなら、1回の検索で約1,500万回の浮動小数点乗算が走ると説明している。
100,000件ならその10倍。
計算式は単純でも、ブラウザのメインスレッド、GC、メモリ配置の影響をまともに受ける。

VecLiteはここをRust/WASMに逃がす。

観点Pure JSVecLite
実行基盤JavaScriptRust/WASM + SIMD
メモリJSオブジェクトやTypedArrayWASM linear memory
GCホットループ中の停止があり得る検索コアにGCなし
データ渡し実装次第で配列がばらけるFloat32Array をフラットに渡す
典型用途小規模検索10k〜100k級のブラウザ内検索

ただし、Rust/WASMにしたら20倍速くなる、という話ではなかった。
原典のベンチマークでは、1,536次元のベクトルに対してVecLiteはPure JSの約3.8〜3.9倍。
10,000件で40ms対152ms、100,000件で400ms対1,576msという結果だった。

ここはむしろ正直な数字だと思う。
V8は Float32Array のタイトなループをかなり最適化する。
VecLiteの差分は「JavaScriptが遅いからRustなら爆速」ではなく、SIMD、メモリ配置、GC停止の予測不能さを削ったぶんの差だ。

WASM境界を雑に跨ぐと全部台無しになる

原典で一番実装っぽかったのは、WASM境界の扱いだ。
Rust/WASM化しても、JSからWASMへ1件ずつ呼び出していたら境界越えのコストで負ける。

VecLiteは境界ルールをかなり強く固定している。

  • upsert は必ずバッチ化する
  • ベクトルはネストした配列ではなく、平坦な Float32Array にする
  • メタデータはJSONにしてから渡す
  • 入力検証はTypeScript側で済ませ、Rust側には壊れた値を渡さない

記事中の比較では、10,000件を1回のWASM呼び出しで渡すと40ms、10,000回に分けると4秒級まで悪化する。
ここはブラウザ内AI部品を作るときの一般則として使える。
WASMは速い計算箱ではあるが、細かい関数呼び出しをたくさん投げるRPCサーバーのように扱うと弱い。

この設計は、WASMとMetalのゼロコピー推論で出てきた話とも近い。
高速化の本体は「言語を変える」より「データをどう渡さないか」「コピーをどう減らすか」に寄る。

高次元EmbeddingではHNSWが常に勝つわけではない

面白いのはHNSWの扱いだ。
ベクトルDBではHNSW(Hierarchical Navigable Small World)が定番で、近似最近傍探索の代表格として出てくる。
大規模なベクトル検索では、全件との距離を計算するflat検索より、グラフを辿って候補を絞るHNSWのほうが速い、という理解になりがちだ。

でもVecLiteのベンチマークでは、1,536次元ではflat indexがHNSWより速かった。
1,000件、5,000件、10,000件の比較で、flatがすべて勝っている。

理由はブラウザ内のスケールと次元数にある。
1,536次元のEmbeddingでは、HNSWの各ホップで見るベクトル自体が重い。
候補数を減らすメリットより、グラフ探索のオーバーヘッドが上回る場面が出る。

低次元かつ巨大件数で更新頻度が低いインデックスなら、HNSWが勝つ余地はある。
ただしブラウザ内で100k件程度、OpenAI系Embeddingの1,536次元前後なら、SIMDで全件をなめるflat検索のほうが速い。

本当に詰まるのは検索後の保存かもしれない

VecLiteの検索速度だけ見ると、100,000件で400msならかなり使えそうに見える。
しかし原典もREADMEも、p99のスパイクと永続化を限界として挙げている。

100,000件 × 1,536次元を f32 で持つと約600MBになる。
f64 なら約1.2GBで、ブラウザではかなり厳しい。
だからVecLiteは f32 固定にしている。
多くのEmbeddingモデルが実質 f32 なので、精度面でも自然な選択だ。

ただし保存時にインデックス全体をJSON blobとしてシリアライズする設計は、50,000件あたりから苦しくなる。
検索は速くても、保存と復元が重い。
ここは今後のchunked persistenceで改善予定とされている。

IndexedDBに保存しても、同一オリジンのJavaScriptから読める点も忘れにくい。
「サーバーに送らない」は強いが、「ブラウザ内なら秘密が完全に守られる」ではない。
ブラウザ拡張、XSS、同一オリジンに置いた別スクリプトのリスクは残る。

Claude Codeで作るならCLAUDE.mdが設計の固定具になる

原典の後半は、VecLiteをClaude Codeで作った話になっている。
ここで効いていたのが CLAUDE.md だ。

作者は、アーキテクチャ、API境界、WASMの制約、セキュリティルール、やらないことを CLAUDE.md に固定していた。
その結果、Claude Codeが「WASMをbase64で埋め込もう」「HNSWを最初から作り直そう」のような、すでに捨てた案へ戻りにくくなったという。

これは Claude Code Best PracticesCLAUDE.md の使い方そのものだ。
AIに実装を任せるほど、作業指示よりも「判断済みの設計を再提案させない」ことが効いてくる。

一方で、Rust SIMD intrinsicsやWASMターゲット対応crateの選定では、人間の確認が必要だったとも書かれている。
rayon のように wasm32-unknown-unknown と噛み合わない依存を提案する、SIMDの水平和や端数処理を間違える、という種類のズレだ。
AIで公開開発する場合、ボイラープレートやテスト生成は任せやすいが、性能クリティカルな境界はレビュー対象として残る。

ブラウザ内RAGが実用に届く場面

VecLite単体ではRAGアプリにならない。
チャンク分割、Embedding生成、Rerank、引用表示、差分更新、アクセス制御は別に要る。
Sentence Transformers v5.4 のようにEmbedding/Reranker側も進んでいるが、ブラウザ内で全部を軽く回せるわけではない。

それでも、検索コアが10k〜100k件を現実的な速度で扱えるなら、できることは増える。

  • ブラウザ拡張が閲覧履歴やローカルメモを端末内で意味検索する
  • Electronアプリがユーザーの文書をクラウドに送らず検索する
  • PWAがService WorkerとIndexedDBでオフライン検索を持つ
  • 小規模な社内ツールが、サーバー側ベクトルDBを立てずに個人単位のRAGを試す

参照