前編の続き:検索の次はLLM
前編では、 RAGの「検索」部分をブラウザだけで動かせることを確認しました。 残るは「生成」——LLM本体です。これもブラウザで動くなら、 サーバー不要のAIアシスタントが完成します。
作り込んだ製品ではなく、実現可能性を確かめるための最小検証です。 問いはシンプル——「小型LLMはブラウザで動くのか、落ちるのか」。 最小モデルから順に、落ちるまで試します。落ちたら、それがその端末の上限だと分かります。
WebLLM は、LLMをブラウザ内でWebGPU実行するライブラリです。 MLC(Machine Learning Compilation)形式のモデルを使い、 Llama・Phi・Gemma・Qwen などをローカルで動かせます。
手持ちのGGUFは使えません——GGUFはllama.cpp専用で、 WebLLMはMLC独自形式です。モデルは実行時にHugging Faceから自動取得・キャッシュされます。
まず動かしてみる(ライブデモ)
以下は実際に動くデモです。あなたの端末で小型LLMをロードし、速度とメモリ限界を実測できます。 モデルはサイズ順に並んでおり、最小モデルから試すことを推奨します。
初回はモデルのダウンロード(315MB〜)が発生し、数分かかります。 Wi-Fi環境を推奨します。端末のメモリによってはクラッシュしますが、それも検証結果です。
LOAD TIME:初回DL+GPU展開(キャッシュ後は短縮)/ PREFILL:入力プロンプトの処理速度/ DECODE:応答生成の速度(tok/s)。これが体感速度で、 10 tok/s を下回ると「待たされている」と感じます。
2つの実機で限界まで試した
Pixel 10(Chrome)と
iPad mini A17 Pro(Safari)で、
5つのモデルを小さい順にロードし、生成まで試しました。
すべて q4f32_1(4bit量子化・fp32演算)を選択——
前編でPixel 10のfp16埋め込みがNaNを返した教訓から、f16を避けました。
| モデル | サイズ | iPad decode | Pixel decode | 品質・判定 |
|---|---|---|---|---|
| Qwen3-0.6B | 315MB | 39.8 | 4.9 | 思考が漏れる(後述) |
| Qwen3.5-0.8B | 420MB | 22.6 | 3.0 | 内容やや不正確 |
| Qwen2.5-1.5B | 787MB | 21.8 | 2.6 | ★品質良好・最も安定 |
| Qwen3-1.7B | 892MB | 20.8 | 17.5 | 多言語で崩壊(後述) |
| Qwen3.5-2B | 1.1GB | iPad: ロード成功→生成でクラッシュ | ★メモリ上限超過 | |
DECODE SPEED: iPad vs Pixel(同じモデル・同じ量子化)
発見①:同じモデルで速度が8倍違う
最も衝撃的だったのは、まったく同じモデルなのに、端末で生成速度が8倍違ったことです。
iPad mini: 21.8 tok/s(サクサク読める)
Pixel 10: 2.6 tok/s(1文字ずつ待つ体感)
差は8.4倍。Pixelの2.6 tok/sは、実用に耐えるレベルではありません。
興味深いのは、両端末とも shader-f16 に対応し、Device Memory も
Pixelは8GBあった点です。スペック上はPixelが不利ではないのに、この差が出ました。
WebGPU経由のLLM推論は、現状Apple GPUに強く最適化されていると考えられます。
同じ「ブラウザで動く」でも、Androidミドル機では体感が伴わない——
これは前編のベクトル検索で見た「カーネルが端末で逆転する」現象と同じ根っこです。
Pixel は
maxStorageBufferBindingSize が 128MB、
iPad は 1024MB でした(8倍差)。
前編のベクトル検索で「87,000文書の壁」を生んだあの制限です。
LLMのような大きな重みを扱う場面では、この上限差も効いてきます。
発見②:reasoningモデルは小型だと暴走する
モデルの品質はパラメータ数に比例しませんでした。 むしろ新しくて大きいはずの Qwen3 系が、小型量子化で壊れました。
思考が漏れる
回答の前に「Okay, the user asked about WebGPU. I need to...」と英語の思考過程がそのまま出力されました。
多言語で完全崩壊
「墙体_Row… proceso truyền classes(COLOR…」と、中国語・スペイン語・タイ語などが混ざった無意味な出力に。
最も安定・高品質
非reasoningモデル。WebGPUの説明を正確・自然な日本語で生成。両端末で最も信頼できました。
Qwen3 は推論(reasoning)モデルで、
<think>...</think> の
思考ブロックを生成してから答えます。しかし0.6B〜1.7Bに4bit量子化すると、
思考を制御しきれず暴走したり、思考タグがそのまま漏れたりします。教訓は明確です——オンデバイスでは、素直な非reasoningモデル(Qwen2.5系)の方が実用的。 デモでは
<think> を検出して思考過程を分離表示し、抑制も試みています。
発見③:ロード成功 ≠ 実用可能
検証の判定基準そのものが、途中で変わりました。当初は「ロードできたか」で見ていましたが、 それでは不十分だと分かったのです。
iPad mini でロードは成功しました。バーは100%になり、"ロード成功" と表示。 ところが最初の質問を投げた瞬間にクラッシュ——タブが落ちました。
原因はKVキャッシュ。生成時には、モデル重みに加えて 文脈を保持するメモリが必要です。重みは載っても、生成で必要な追加メモリで溢れるのです。
そこでデモの判定を「生成まで成功して初めて実用可能」に改めました。 ロードのプログレスバーは、実用性の保証にはなりません。
// ❌ ロード成功で判定してしまうと 2B が「可」になる(実際は生成で落ちる) // ✅ 生成の try が最後まで通って初めて recordVerdict(true) try { for await (const chunk of stream) { /* ...トークン受信... */ } recordVerdict(true); // 生成完了 = 実用可能 } catch (e) { recordVerdict(false); // 生成でクラッシュ = メモリ上限超過 }
見落とせない現実:ロードに数分かかる
速度と品質の裏で、初回ロード時間が実用性に重くのしかかります。
| モデル(1.5B) | iPad | Pixel | DL容量 |
|---|---|---|---|
| Qwen2.5-1.5B ロード | 223秒 | 279秒 | 787MB |
| = 約 | 3分43秒 | 4分39秒 | 初回のみ |
2回目以降はブラウザキャッシュから読むので速くなりますが、 初回訪問者は数分待つことになります。 一般的なWebサイトのAIアシスタントとしては、この初回コストは致命的です。 「サーバー不要」の代償が、そのままユーザー体験に跳ね返ります。
結論:動く。だが「完結」は選ばない
2つの実機で限界まで試した結論です。 ブラウザ完結LLMは、技術的には確かに動きます。 iPad mini なら1.5Bモデルが 21 tok/s・良好な品質で動作しました。これは事実として大きい。
しかし実用の壁は高いと分かりました。3つの現実が重なります。
端末依存が激しい
同じモデルで速度が8倍違う。iPadで実用的でも、Androidミドル機では2.6 tok/sで使いものにならない。
初回ロードが重い
1.5Bで4分前後・787MBのダウンロード。初回訪問者の離脱は避けられない。
品質と安定性
安定して使えるのは1.5Bの非reasoningモデルまで。それ以上は品質崩壊かクラッシュ。
検証の答えは出ました。検索はブラウザで、生成はサーバーで。
前編で実証したブラウザのベクトル検索は軽量・高速・端末非依存。 一方生成は、VPS上の安定したLLM(llama.cpp + Qwen2.5-1.5B)に任せる。 ブラウザで暴走したreasoningも、サーバー側なら制御できます。
「すべてブラウザで」という理想は美しいですが、実測が示したのは分業の合理性でした。
前編:ブラウザのベクトル検索は実用的(軽量・高速・GPUで加速)。
後編:ブラウザのLLM生成は動くが実用は苦しい(端末依存・重い・不安定)。
→ 次は、この2つの結論を統合したハイブリッドRAGの設計に進みます。