「ローカルRAGを組みたいが、埋め込みバッチにどれくらい時間がかかるのか分からない」——ベンチマーク記事はたいてい他人のデータセットか、せいぜい数十件のサンプルで測っている。自社には無人運転で日々書き溜めてきたMarkdownが千本単位である。それをそのまま埋め込みに投げたら何分かかるのか、M1 Max 64GBで実測した。
結論から言うと、1,325件のMarkdownを1件ずつ逐次で埋め込むと約5分46秒で終わった。だが数字そのものより、測る過程で踏んだ2つの罠——GPU調停スクリプトを自分にかけて自滅した話と、「文字数でトリムすれば安全」という思い込みが7件で崩れた話——のほうが再現性のある学びだった。
何を測ったか
対象は自社の業務用リポジトリ配下にある .md ファイル全部。node_modules・graphify-out・.next*・_archive 系の生成物/依存物は除外し、実際に人とAIが書いた文書だけを数えた。
find . -name "*.md" \
-not -path "*/node_modules/*" -not -path "*/graphify-out/*" \
-not -path "*/.next*/*" -not -path "*/_archive/*" | wc -l
実行するたびに 1,325〜1,327件と件数がわずかにブレた。ドキュメント生成が今この瞬間も進行中で、コーパスが静的でないことの証拠だ。
モデルは ollama list で確認した bge-m3:latest(1.2GB、当日追加済み)。Ollamaのローカル埋め込みエンドポイント /api/embed を直接POSTで叩き、各ファイルの先頭8,000文字を送る素朴な実装で測った。
結果 — 3回実測の実測値
Run対象ファイル数成功失敗総時間docs/sec
11,3251,3187346.16秒3.808
21,3251,3187354.51秒3.718
31,3271,3207344.17秒3.835
総時間の中央値は346.16秒(約5分46秒)。最速はRun3の344.17秒、最遅はRun2の354.51秒——3回の振れ幅は10秒程度で、平均だけでなく両端を見ても大きなブレは無かった。docs/secの中央値は3.808件/秒。処理した総文字数は約394万文字。
失敗は3回ともちょうど7件で一致していた。件数が毎回同じという時点で、単発のネットワーク不調ではなく構造的な原因があると見て、後述の通り切り分けた。
メモリはどこまで伸びたか — VRAMではなくRSSを代理指標に使った
Apple Siliconはユニファイドメモリで、GPU専用VRAMを直接計測するコマンドが手元に無かった。手元にあるgpucheck.shはGPU Device Utilization%(稼働率)を見るツールであって、メモリ使用量は測れない。そこで代理指標として、1秒間隔でps -axo rss,commをサンプリングし、Ollamaプロセスの常駐メモリ(RSS)の最大値を追った。
3回ともピークRSS 7,839.3MBで完全に一致した。これは埋め込みのたびにメモリが積み上がっているのではなく、常駐しているOllamaサーバプロセスがモデルロード後にそのメモリ量で安定していることを示す——3回とも同一プロセスを使い回した実行だったための一致であり、プロセスを再起動すれば変わりうる数字だという点は明記しておく。
詰まった所①: GPU調停スクリプトを自分にかけて自滅した
全社共通のGPU調停ルールに従い、最初は生成ジョブを gpu-signal.sh run <名前> "<コマンド>" でラップして実行した。結果は1件も進まずタイムアウトで終了した。
[freeze] llama-server PID=24908 凍結(GPU Compute解放)
[freeze] ollama serve PID=1148 凍結
✅ GPUをComfyUI/LTXに明け渡し完了。生成を開始できます。
...
TimeoutError: timed out
原因は単純だった。この調停スクリプトは「ComfyUI/LTX/ACE-StepのようなGPUツールにGPUを明け渡すためにOllamaを凍結する」設計であり、今回のジョブはOllama(bge-m3)自身を叩くベンチだった。信号機を通した瞬間、自分が呼び出そうとしているサーバーを自分で止めてしまった。
学びはシンプルで、「GPUを使うジョブは必ず調停スクリプトを通す」という運用ルールには例外がある。調停の対象は「Ollama vs 他のGPUツール」であって、「Ollamaそのものを使うジョブ」はそもそも調停の対象外。信号機を外して直接実行したところ、3回の計測は問題なく完走した。
詰まった所②: 「8,000文字トリムで安全」という前提が7件で崩れた
3回とも同じ7件が失敗するので、個別に再送信して切り分けた。まずUTF-8のstrictデコードを全件チェックしたが、エラーは0件——文字コードが原因ではない。
次に失敗した7件だけ単独で/api/embedに投げ直すと、次のエラーが返ってきた(3件で直接確認、残り4件も同一の失敗パターン)。
{"error":"the input length exceeds the context length"}
ollama show bge-m3で確認したモデルの実測スペックは次の通り。
architecture bert
parameters 566.70M
context length 8192
embedding length 1024
quantization F16
コンテキスト長は8,192トークン。当初「8,000文字にトリムすれば8,192トークンには収まるだろう」という前提でコードを書いていたが、これは誤りだった。失敗した7件はコードブロックや表・記号を多く含む長めのMarkdownで、同じ8,000文字でもトークン化効率が悪く、8,192トークンを超えていた。
文字数によるトリムは、トークン数上限の安全な代理指標にならない。特に日本語やコードブロックが混在する文書では、文字数とトークン数の比率が文書ごとに変わる。実運用でこの罠を避けるには、文字数ではなくモデルのcontext lengthを基準にした事前のトークンカウント(またはより保守的な文字数上限)が必要になる——今回はベンチの目的が「素朴な実装での実測」だったため、失敗した7件をエラーとして記録するに留め、リトライやチャンク分割の実装までは踏み込んでいない。
ローカルRAG vs 構造グラフ検索 — 1,325件規模での使い分け
自社にはすでに、埋め込みを一切使わない検索基盤がある。AST由来の知識グラフgraphify-out/graph.jsonだ。
import json
d = json.load(open('graphify-out/graph.json'))
print(len(d['nodes'])) # 5934
2026-08-03時点でノード数は5,934。これはコード/ドキュメントの構造的な関係(誰が誰を参照しているか)を機械的に抽出したグラフで、ベクトル類似度検索は行わない。過去のメモリには「20,755ノード」という記録も残っていたが、これはより古い時点のスナップショットであり、今回は自分でファイルを読み直して現在値を使った——数字は記憶ではなく実測を信じる、という原則をここでも適用した。
bge-m3による埋め込みが向いているのは「意味が近い文書を探す」曖昧検索で、graphifyが向いているのは「このシンボルはどこから参照されているか」という構造的な検索だ。今回の実測で分かったのは、1,325件規模なら埋め込み生成自体は6分弱の低コスト作業ということ。つまりローカルRAGの導入コストにおいて、埋め込み計算そのものはボトルネックになりにくい。ボトルネックになるとすれば、今回踏んだような「トークン上限の見積もりミス」や「調停ロジックの誤適用」といった、実装の詰めの甘さの方だった。
まとめ
自社Markdown 1,325件のbge-m3埋め込みは、1件ずつの逐次実行で約5分46秒(中央値346.16秒)、docs/sec中央値3.808件/秒
Ollamaプロセスのピーク常駐メモリ(RSS)は3回とも7,839.3MBで一致(VRAM専用の直接計測はできず代理指標)
GPU調停スクリプトは「Ollama vs 他のGPUツール」の調停であり、Ollama自身を使うジョブに適用すると自滅する
「文字数でトリムすれば安全」は誤り。コンテキスト長はトークン単位(今回は8,192)であり、コードブロックや表を含む文書では文字数とトークン数の比率が崩れて超過しうる
1,325件規模なら埋め込み生成自体はボトルネックにならない。詰まるのは大抵、実装側の見積もりミスだった
Top comments (0)