sqlite-vec・ObjectBox・LanceDBで実装するオンデバイスRAGのベクトル検索レイテンシ最適化
この記事でわかること
- オンデバイスRAGにおけるベクトル検索の3つのアーキテクチャパターンと、それぞれのレイテンシ特性
- sqlite-vec(brute-force + binary quantization)、ObjectBox(HNSW)、LanceDB(IVF_HNSW_SQ)の実装方法と性能比較
- binary quantizationとHamming距離計算によるsqlite-vecの15〜45倍高速化テクニック
- 100k〜1Mベクトル規模でのインデックス選定判断フレームワーク
- EmbeddingGemma 300Mを使ったモバイル向け埋め込みパイプラインの構築手順
対象読者
- 想定読者: オンデバイスAIやエッジRAGの導入を検討しているMLエンジニア
-
必要な前提知識:
- Python 3.11以降の基本的な使い方
- ベクトル検索(kNN/ANN)の基本概念
- RAG(Retrieval-Augmented Generation)パイプラインの構成要素
- SQLiteの基本操作
結論・成果
オンデバイスRAGにおけるベクトル検索は、2026年時点でsub-5msのレイテンシが達成可能な段階に到達しています。sqlite-vecのbinary quantizationを適用すると、100kベクトル規模でfloat32の75〜214msから11msへと最大19倍の高速化が報告されています(sqlite-vec公式ブログ)。ObjectBoxは5年前のミッドレンジスマートフォン(LG G8S)でも0.25〜0.27ms/queryで約4,000 QPSを達成し(ObjectBox公式)、LanceDBはIVF_HNSW_SQインデックスでディスクベース運用しつつ高リコールを維持できます。
エンドツーエンドのRAGパイプラインでは、検索レイテンシよりもLLM生成が支配的なボトルネックとなるため、検索部分を5ms以下に抑えることが実用上の目標値となります。
オンデバイスベクトル検索の3つのアーキテクチャパターンを理解する
2026年、オンデバイスRAGが本格的に実用化されています。企業のデータレジデンシ要件やプライバシー懸念から、埋め込みベクトルをクラウドに送信せずデバイス上で完結させるアーキテクチャが求められています。Snapdragon X EliteのNPU(45 TOPS INT8)上で動作するオンデバイスRAGパイプラインでは、NPU活用時にembedding生成スループットが9.1倍、エンドツーエンドレイテンシが4.0倍改善されたと報告されています(arxiv: Energy-Efficient On-Device RAG)。
オンデバイスベクトル検索には、大きく3つのアーキテクチャパターンがあります。それぞれトレードオフが異なるため、データ規模・デバイス制約・精度要件に応じた選択が必要です。
パターン1: Brute-force + 量子化(sqlite-vec)
sqlite-vecはSQLiteの拡張として動作し、ANN(近似最近傍探索)インデックスを持たず、brute-force(全探索)で検索を行います。一見非効率に思えますが、binary quantizationとHamming距離計算を組み合わせることで、実用的な速度を実現しています。
アーキテクチャの特徴:
| 項目 | 詳細 |
|---|---|
| 検索方式 | brute-force(全探索) |
| 距離関数 | L2(vec_distance_l2)、cosine(vec_distance_cosine) |
| 量子化 | binary quantization(vec_quantize_binary) |
| メモリプロファイル | デフォルト30MB |
| バイナリサイズ | SQLite拡張として数百KB |
| ライセンス | MIT / Apache-2.0 |
brute-forceは**リコール100%**を保証する点が特徴です。ANNインデックスでは構造上リコールが100%に達しないため、精度を最優先するユースケースではbrute-forceが適しています。ただし、ベクトル数が増えるとリニアにレイテンシが増加するため、100kベクトルを超える規模ではbinary quantizationの適用が事実上必須となります。
注意点:
sqlite-vecは現時点(v0.1.x)でHNSWやIVFなどのANNインデックスをサポートしていません。100万ベクトルを超える規模では、brute-force検索のレイテンシが許容範囲を超える可能性があります。大規模データにはObjectBoxやLanceDBの利用を検討してください。
パターン2: HNSW組み込み型(ObjectBox)
ObjectBoxは、モバイル・IoT向けに設計されたオンデバイスデータベースで、バージョン4.0からHNSW(Hierarchical Navigable Small World)ベースのベクトル検索を内蔵しています。データベースエンジンとHNSWインデックスが深く統合されており、全ベクトルをメモリに保持する必要がない点が特徴です。
アーキテクチャの特徴:
| 項目 | 詳細 |
|---|---|
| 検索方式 | HNSW(近似最近傍探索) |
| 距離関数 | Euclidean、Cosine、Dot product、Haversine |
| HNSWパラメータ | neighborsPerNode(M): デフォルト30、indexingSearchCount(efConstruction): デフォルト100 |
| メモリ消費 | <8MB(バイナリ)、KBクラスの動的RAM |
| 対応言語 | Python、Java/Kotlin、Swift、Flutter/Dart、C/C++、Go |
ObjectBoxのHNSW実装は、ディスクベースの多層キャッシュを採用しています。頻繁にアクセスされるベクトルはメモリ上に保持され、それ以外はディスクからフェッチされます。このため、デバイスの物理メモリを超えるデータセットでも動作可能です(ObjectBox公式ドキュメント)。
パターン3: カラムナストレージ + 複合インデックス(LanceDB)
LanceDBは、Apache Arrow互換のLanceカラムナフォーマットをベースとした組み込みベクトルデータベースです。IVF、HNSW、Product Quantization(PQ)、Scalar Quantization(SQ)、RaBitQ(RQ)を組み合わせた複合インデックスを構築できます。
アーキテクチャの特徴:
| 項目 | 詳細 |
|---|---|
| 検索方式 | IVF_FLAT / IVF_PQ / IVF_HNSW_SQ / IVF_HNSW_PQ / IVF_RQ |
| 距離関数 | L2、Cosine、Dot product、Hamming |
| ストレージ | Lance columnar format(Apache Arrow互換) |
| メモリモデル | ディスクベース(zero-copy、メモリマップ) |
| 対応言語 | Python、TypeScript、Rust、REST API |
LanceDBの公式ドキュメントでは、リコールとレイテンシのバランスにおいてIVF_HNSW_SQが推奨されています(LanceDB Vector Index docs)。SQ(Scalar Quantization)による圧縮比は約1/4で、PQの1/16〜1/64ほどの圧縮率はないものの、リコールの低下が小さいという利点があります。
sqlite-vecでbinary quantizationによる高速検索を実装する
sqlite-vecの最大の武器は、binary quantizationとHamming距離計算の組み合わせです。float32ベクトル(1次元あたり4バイト)をbit vector(1次元あたり1ビット)に圧縮することで、ストレージを32倍削減し、Hamming距離による高速なビット演算で検索を実行します。
基本的なベクトル検索の実装
まず、sqlite-vecの基本的な使い方を確認しましょう。以下はfloat32ベクトルを使った標準的なKNN検索の実装です。
# sqlite-vec基本実装: float32ベクトルによるKNN検索
import sqlite3
import sqlite_vec
import struct
from typing import List
def serialize_f32(vector: List[float]) -> bytes:
"""float32ベクトルをバイナリ形式にシリアライズ"""
return struct.pack(f"{len(vector)}f", *vector)
db = sqlite3.connect(":memory:")
db.enable_load_extension(True)
sqlite_vec.load(db)
db.enable_load_extension(False)
# 384次元のベクトルテーブルを作成
db.execute("""
CREATE VIRTUAL TABLE doc_vectors
USING vec0(embedding float[384])
""")
# ベクトル挿入
with db:
for i, embedding in enumerate(embeddings):
db.execute(
"INSERT INTO doc_vectors(rowid, embedding) VALUES (?, ?)",
[i, serialize_f32(embedding)],
)
# KNN検索(L2距離、上位5件)
query_vec = serialize_f32(query_embedding)
results = db.execute("""
SELECT rowid, distance
FROM doc_vectors
WHERE embedding MATCH ?
ORDER BY distance
LIMIT 5
""", [query_vec]).fetchall()
binary quantizationによる高速化
次に、binary quantizationを適用して検索を高速化します。vec_quantize_binary() 関数は、正の値を1、負の値を0に変換し、ビット列にパッキングします。1024次元のfloat32ベクトル(4,096バイト)が128バイトのbit vectorに圧縮されます。
# binary quantizationを適用した高速検索
import sqlite3
import sqlite_vec
import struct
import numpy as np
from typing import List
def serialize_f32(vector: List[float]) -> bytes:
return struct.pack(f"{len(vector)}f", *vector)
db = sqlite3.connect("rag_vectors.db")
db.enable_load_extension(True)
sqlite_vec.load(db)
db.enable_load_extension(False)
# bit vectorテーブルを作成(1024次元 → 128バイト)
db.execute("""
CREATE VIRTUAL TABLE doc_vectors_bin
USING vec0(embedding bit[1024])
""")
# float32をbinary quantizationしてから挿入
with db:
for i, embedding in enumerate(embeddings):
db.execute("""
INSERT INTO doc_vectors_bin(rowid, embedding)
VALUES (?, vec_quantize_binary(?))
""", [i, serialize_f32(embedding)])
# Hamming距離による高速検索
query_vec = serialize_f32(query_embedding)
results = db.execute("""
SELECT rowid, distance
FROM doc_vectors_bin
WHERE embedding MATCH vec_quantize_binary(?)
ORDER BY distance
LIMIT 10
""", [query_vec]).fetchall()
なぜbinary quantizationを選ぶのか:
- float32 → bit変換でストレージ32倍削減(1024次元: 4,096B → 128B)
- Hamming距離計算はXOR + popcount命令で実行され、現代CPUのSIMD命令で高速処理可能
- sqlite-vec公式ブログによると、3072次元・100kベクトルでfloat32が214msに対し、bit vectorは11msで検索完了(sqlite-vec v0.1.0 release)
注意点:
binary quantizationは各次元を1ビットに圧縮するため、リコールが5〜10%程度低下する傾向があります(ObjectBox公式記事)。高精度が必要な場合は、binary quantizationで候補を粗く絞り込み(oversampling)、float32で再スコアリングする2段階検索パターンが有効です。
2段階検索(oversampling + reranking)の実装
binary quantizationのリコール低下を補うために、候補を多めに取得してからfloat32で再スコアリングするパターンを実装してみましょう。
# 2段階検索: binary quantizationで粗絞り → float32でリランク
OVERSAMPLE_FACTOR = 5
TOP_K = 10
# Stage 1: Hamming距離で候補を多めに取得
candidates = db.execute("""
SELECT rowid, distance
FROM doc_vectors_bin
WHERE embedding MATCH vec_quantize_binary(?)
ORDER BY distance
LIMIT ?
""", [serialize_f32(query_embedding), TOP_K * OVERSAMPLE_FACTOR]).fetchall()
candidate_ids = [row[0] for row in candidates]
# Stage 2: float32テーブルからcosine距離で再スコアリング
placeholders = ",".join("?" * len(candidate_ids))
reranked = db.execute(f"""
SELECT rowid, vec_distance_cosine(embedding, ?) as dist
FROM doc_vectors_float
WHERE rowid IN ({placeholders})
ORDER BY dist
LIMIT ?
""", [serialize_f32(query_embedding)] + candidate_ids + [TOP_K]).fetchall()
この2段階アプローチにより、Stage 1の高速なHamming距離検索で候補を50件に絞り、Stage 2でfloat32の精密なcosine距離計算を上位10件に絞り込みます。binary quantization単体と比べてリコールを回復しつつ、全探索のfloat32検索よりも大幅にレイテンシを削減できます。
ObjectBoxのHNSWパラメータチューニングでモバイルレイテンシを最適化する
ObjectBoxはHNSWインデックスを内蔵しており、ベクトル挿入時に自動的にグラフが構築されます。モバイルデバイスでの実用的なレイテンシ最適化の鍵は、HNSWパラメータの適切な調整にあります。
ObjectBoxのベクトル検索セットアップ
ObjectBoxはPythonバインディングを提供していますが、API は安定化途中である点に注意が必要です。以下はPythonでの基本的なセットアップ例です(ObjectBox Python docs)。
# ObjectBoxでのベクトル検索セットアップ(Python)
import objectbox
import numpy as np
# エンティティ定義(HNSWインデックス付き)
@objectbox.Entity(id=1, uid=1)
class Document:
id = objectbox.Id(id=1, uid=1001)
title = objectbox.Property(str, id=2, uid=1002)
embedding = objectbox.Property(
np.ndarray,
id=3,
uid=1003,
index=objectbox.HnswIndex(
dimensions=768,
distance_type=objectbox.HnswDistanceType.COSINE,
neighbors_per_node=30, # M: グラフの接続密度
indexing_search_count=200, # efConstruction: 構築時の精度
),
)
# データベース初期化
store = objectbox.Store(
model=objectbox.Model(),
directory="./objectbox_rag_db",
)
box = store.box(Document)
# ベクトル挿入
doc = Document()
doc.title = "ドキュメントタイトル"
doc.embedding = embedding_vector # 768次元のnumpy配列
box.put(doc)
# 最近傍検索
query = box.query(
Document.embedding.nearest_neighbor(query_vector, max_count=10)
).build()
results = query.find_with_scores()
HNSWパラメータの調整指針
HNSWのパラメータは構築時のパラメータと検索時のパラメータに分かれます。それぞれの調整がレイテンシとリコールに与える影響を理解することが重要です。
| パラメータ | デフォルト値 | 説明 | 大きくすると | 小さくすると |
|---|---|---|---|---|
| M(neighborsPerNode) | 30 | ノードあたりの最大接続数 | リコール↑、メモリ↑、構築時間↑ | レイテンシ↓、メモリ↓、リコール↓ |
| efConstruction | 100 | 構築時の探索幅 | インデックス品質↑、構築時間↑ | 構築高速化、リコール↓ |
| ef(検索時) | 未公開(実行時指定) | 検索時の探索幅 | リコール↑、レイテンシ↑ | レイテンシ↓、リコール↓ |
よくある間違い:
最初はMの値を大きくすれば精度が上がると考えがちですが、モバイルデバイスではメモリ制約が厳しいため、M=16〜30の範囲に収めるのが現実的です。ObjectBoxの公式ドキュメントでは、efConstructionは「少なくとも200」が推奨されています(ObjectBox docs)。M=16で高速化し、efConstructionを200に上げることでリコールを維持するのがバランスのよいアプローチです。
モバイルデバイスでの実測パフォーマンス
ObjectBox公式の記事では、LG G8S(2019年発売のミッドレンジ端末)で以下のパフォーマンスが報告されています(ObjectBox: On-device vector databases in 2026)。
| 指標 | 値 |
|---|---|
| クエリレイテンシ | 0.25〜0.27 ms/query |
| スループット | 約4,000 QPS |
| バイナリサイズ | <8 MB |
| 動的RAM使用量 | KBクラス |
この数値は5年前の端末でのものです。2026年のフラッグシップ端末(Apple A19 Pro、Snapdragon 8 Elite、Google Tensor G5など)は専用NPUを搭載しており、dot product・行列演算の高速化が可能なため、さらに良いパフォーマンスが期待できます。
制約条件:
ObjectBoxのPython APIは「non-final and subject to change」と明記されています。プロダクション利用ではJava/Kotlin(Android)やSwift(iOS)、Flutter/Dartのバインディングを推奨します。また、2026年8月時点でベクトルの量子化(int8/binary等)は未サポートで、float32のみ対応しています。
LanceDBのIVF_HNSW_SQインデックスでディスクベースRAGを構築する
LanceDBは、メモリに収まらない大規模データセットを扱う場合に強みを発揮します。Lance columnar formatによるzero-copyアクセスとディスクベース運用により、デバイスのRAM制約を超えたデータセットでも性能の崖を回避できます。
LanceDBの基本セットアップとインデックス構築
# LanceDBのセットアップとIVF_HNSW_SQインデックス構築
import lancedb
import numpy as np
import pyarrow as pa
# ローカルディレクトリにデータベースを作成
db = lancedb.connect("./lancedb_rag")
# テーブル作成とデータ挿入
data = pa.table({
"id": range(len(embeddings)),
"text": documents,
"vector": [emb.tolist() for emb in embeddings],
})
table = db.create_table("documents", data)
# IVF_HNSW_SQインデックスを構築
# num_partitions: sqrt(N)が目安、num_sub_vectors: dim // 8
table.create_index(
metric="cosine",
index_type="IVF_HNSW_SQ",
num_partitions=32, # データ量に応じて調整
ef_construction=150, # 構築時の探索幅
)
# インデックス構築完了を待機
table.wait_for_index(["documents_idx"])
print(table.index_stats("documents_idx"))
検索パラメータの最適化
LanceDBの検索では、nprobesとrefine_factorが重要なチューニングパラメータです。
# 検索パラメータの最適化
results = (
table.search(query_embedding)
.metric("cosine")
.limit(10)
.nprobes(20) # 探索するパーティション数(多いほど高リコール)
.refine_factor(5) # 量子化ベクトルの再スコアリング倍率
.to_pandas()
)
| パラメータ | 説明 | 推奨値 | トレードオフ |
|---|---|---|---|
| nprobes | 探索するIVFパーティション数 | num_partitions × 5〜10% | 多いとリコール↑レイテンシ↑ |
| refine_factor | SQ/PQ量子化後の再スコアリング倍率 | 5〜10 | SQ使用時はリコール回復に必須 |
| ef | HNSW検索時の探索幅 | 128〜256 | 大きいほどリコール↑ |
なぜIVF_HNSW_SQを選ぶのか:
- IVF_HNSW_FLAT: リコール最高だが、ベクトルを圧縮しないためメモリ消費大
- IVF_HNSW_SQ: 1/4の圧縮比でリコール低下が小さい(公式推奨の「best recall/latency trade-off」)
- IVF_PQ: 1/16〜1/64の高圧縮だが、256次元以下でないとリコール低下が顕著
- IVF_RQ(RaBitQ): 最大1/32圧縮で新しいアプローチだが、エコシステムがまだ成熟途上
FTS + ベクトルのハイブリッド検索
LanceDBはBM25全文検索とベクトル検索を組み合わせたハイブリッド検索をサポートしています。オンデバイスRAGでは、キーワードの完全一致が重要な技術ドキュメント検索において特に有効です。
# ハイブリッド検索: BM25 + ベクトル検索
# FTSインデックスの構築
table.create_fts_index("text")
# ハイブリッド検索の実行
results = (
table.search(query_embedding, query_type="hybrid")
.text("検索キーワード")
.limit(10)
.to_pandas()
)
sqlite-vecでもFTS5との組み合わせでハイブリッド検索が可能です。SQLite RAGの実装例では、Reciprocal Rank Fusionでキーワード検索と意味検索の結果を統合し、クエリ応答サイクルが平均370msで動作したと報告されています(Building a RAG on SQLite)。
3つのDBの性能特性を比較して選定する
ここまで見てきた3つのオンデバイスベクトルDBの特性を整理し、ユースケースに応じた選定基準を提示します。
性能比較表
| 項目 | sqlite-vec | ObjectBox | LanceDB |
|---|---|---|---|
| 検索方式 | brute-force | HNSW | IVF_HNSW_SQ |
| 100kベクトル検索レイテンシ | 11ms(bit)/ 75-214ms(float) | 0.25ms(HNSW) | インデックス依存 |
| リコール | 100%(brute-force) / 90-95%(binary quant) | 95-99%(HNSW設定依存) | 95-99%(SQ + refine_factor) |
| メモリ消費 | 30MB(デフォルト) | <8MB + KBクラス動的RAM | ディスクベース(zero-copy) |
| 量子化 | binary(32x圧縮) | 未サポート(float32のみ) | SQ(1/4)/ PQ(1/16-1/64)/ RQ(1/32) |
| ANN対応 | なし(brute-force only) | HNSW | IVF + HNSW + 量子化 |
| ハイブリッド検索 | FTS5連携で可能 | 未対応 | BM25 FTS内蔵 |
| 対応言語 | Python, Node.js, Ruby, Rust, Go, WASM | Python, Java, Kotlin, Swift, Flutter, C++ | Python, TypeScript, Rust |
| ライセンス | MIT / Apache-2.0 | Apache-2.0(Server: 独自) | Apache-2.0 |
選定フローチャート
ユースケース別の推奨選定
sqlite-vecが最適なケース:
- SQLiteを既に使用しているアプリケーションへのベクトル検索追加
- ベクトル数が10万以下でリコール100%が必要な精密検索
- WebAssemblyでブラウザ内動作が必要な場合
- テナント分離が必要な場合(テナントごとにSQLiteファイルを分割)
ObjectBoxが最適なケース:
- Flutter/Dartで開発するモバイルアプリ(Android/iOS)
- サブミリ秒のレイテンシが必要なリアルタイム検索
- オフライン対応 + データ同期が必要なエッジデバイス
- バイナリサイズの制約が厳しい組み込みシステム(<8MB)
LanceDBが最適なケース:
- 100万ベクトル以上の大規模データセット
- デバイスのRAMに収まらないデータのディスクベース処理
- マルチモーダルデータ(画像、テキスト、メタデータ)の統合管理
- Apache Arrow/Pandas/DuckDBとのデータパイプライン統合
オンデバイス埋め込みパイプラインを構築する
ベクトルDBの選定と最適化だけでなく、埋め込みモデルの選択もオンデバイスRAGのレイテンシに大きく影響します。2026年時点でのモバイル向け埋め込みモデルの標準はEmbeddingGemma 300Mです。
EmbeddingGemma 300Mの特徴
GoogleがリリースしたEmbeddingGemma(Google Developers Blog)は、Gemma 3アーキテクチャをベースとした308Mパラメータの軽量埋め込みモデルです。
| 項目 | 値 |
|---|---|
| パラメータ数 | 308M |
| 出力次元 | 128〜1024(可変) |
| QAT適用後のRAM | <200MB |
| 対応ランタイム | ONNX Runtime Mobile、MediaPipe |
ハマりポイント:
当初、768次元の埋め込みモデル(all-MiniLM-L6-v2等)を使用していましたが、EmbeddingGemmaは出力次元を128〜1024の範囲で動的に選択できます。次元数を下げるとストレージと検索レイテンシが改善される一方、検索精度が低下します。384次元がストレージ効率と精度のバランスポイントとして報告されていますが、ユースケースに応じたベンチマークが必要です。
埋め込みパイプライン全体像
オンデバイスRAGの埋め込みパイプラインは以下の構成です。
# オンデバイス埋め込みパイプラインの構成例
from onnxruntime import InferenceSession
from transformers import AutoTokenizer
import numpy as np
class OnDeviceEmbedder:
def __init__(self, model_path: str, dim: int = 384):
self.session = InferenceSession(
model_path,
providers=["CPUExecutionProvider"],
)
self.tokenizer = AutoTokenizer.from_pretrained(
"google/embeddinggemma-308m"
)
self.dim = dim
def embed(self, texts: list[str]) -> np.ndarray:
inputs = self.tokenizer(
texts,
padding=True,
truncation=True,
max_length=512,
return_tensors="np",
)
outputs = self.session.run(
None,
{
"input_ids": inputs["input_ids"].astype(np.int64),
"attention_mask": inputs["attention_mask"].astype(np.int64),
},
)
# Matryoshka: 出力次元をスライスして削減
embeddings = outputs[0][:, :self.dim]
# L2正規化
norms = np.linalg.norm(embeddings, axis=1, keepdims=True)
return embeddings / norms
# sqlite-vecと組み合わせた使用例
embedder = OnDeviceEmbedder("embeddinggemma_int4.onnx", dim=384)
query_vec = embedder.embed(["RAGの実装方法"])[0]
Snapdragon X EliteのNPU上でEmbeddingGemma 300Mを動作させた実験では、CPUと比較してembedding生成スループットが**3,325 tokens/s vs 367 tokens/s(9.1倍)**に向上し、消費エネルギーも12.3倍削減されたと報告されています(arxiv: Energy-Efficient On-Device RAG on Snapdragon X Elite)。
よくある問題と解決方法
| 問題 | 原因 | 解決方法 |
|---|---|---|
| sqlite-vecでMATCH句がエラー | 拡張が未ロード |
sqlite_vec.load(db) を接続直後に実行 |
| binary quantizationでリコール低下 | 1bit量子化の精度損失 | oversampling(5〜10倍取得)+ float32リランク |
| ObjectBoxのインデックス構築が遅い | efConstruction値が大きすぎる | efConstruction=100〜200の範囲で調整 |
| LanceDBでインデックス作成失敗 | データ行数が少なすぎる | 最低数千行以上のデータでインデックス作成 |
| LanceDBのnprobes調整で改善しない | パーティション数とのバランス不良 | nprobes = num_partitions × 5〜10%を目安に |
| モバイルでONNXモデルのロードが遅い | 非量子化モデルの使用 | INT4量子化(Olive toolkit)を適用 |
| 検索レイテンシよりLLM生成が遅い | 検索は最適化済み | LLM側の量子化・KVキャッシュ最適化にフォーカス |
まとめと次のステップ
まとめ:
- sqlite-vec: brute-force検索だがbinary quantizationで実用的な速度(100kベクトルで11ms)を実現。SQLite互換性とリコール100%が強み。100k以下の規模でシンプルさを重視する場合に最適
- ObjectBox: HNSW内蔵で0.25ms/queryのサブミリ秒レイテンシ。モバイル・IoT向けの<8MBフットプリントとデータ同期機能が特徴。Flutter/Dartエコシステムとの親和性が高い
- LanceDB: IVF_HNSW_SQによるリコール/レイテンシのバランスとディスクベース運用が強み。100万ベクトル以上の大規模データやマルチモーダルデータに適する
- オンデバイスRAGでは検索レイテンシ(sub-5ms達成可能)よりもLLM生成がボトルネック。検索最適化は「十分に速い」を目指し、残りの最適化予算をLLM側に投入するのが合理的
次にやるべきこと:
- 自分のデータセット規模(ベクトル数・次元数)で3つのDBをベンチマークし、レイテンシ・リコール・メモリ消費を実測する
- EmbeddingGemma 300MのONNX Runtime Mobile版を導入し、デバイス上での埋め込み生成パイプラインを構築する
- binary quantization + oversamplingのリコール回復率を、自分のドメインデータで検証する
参考
- sqlite-vec v0.1.0 stable release - Alex Garcia's Blog
- Building a RAG on SQLite - SQLite Blog
- ObjectBox: On-device vector databases in 2026
- ObjectBox On-Device Vector Search - Docs
- ObjectBox: On-Device Vector Database for Mobile, IoT
- LanceDB Vector Index Documentation
- LanceDB GitHub Repository
- Energy-Efficient On-Device RAG on Snapdragon X Elite - arXiv
- Introducing EmbeddingGemma - Google Developers Blog
- SQLite Just Got Vector Search - DEV Community
- On-Device RAG for App Developers - Medium
注意: この記事はAI(Claude Code)により自動生成されました。内容の正確性については複数の情報源で検証していますが、実際の利用時は公式ドキュメントもご確認ください。