ブラウザでLLMを動かす:WebGPU、Transformers.js、Chrome内蔵AIを徹底解説
いま作っているAI機能はすべてAPIコールです。ユーザーがプロンプトを入力すると、サーバーがOpenAIに転送し、800ms〜3秒待ち、$0.01〜0.50支払い、レスポンスを返す。DAU1万のチャット機能なら、API費用だけで月$3,000〜15,000——サーバーインフラ代は含めずに。
でも、モデルがユーザーのデバイスで動いたら?APIコールなし。計算以外のレイテンシなし。推論ごとのコストなし。ユーザーデータがブラウザの外に出ることもなし。
これはもう仮定の話ではありません。2026年、ブラウザは本格的なAI推論ランタイムになりました。WebGPUがネイティブに近いGPUアクセスを提供し、2GB以下の量子化モデルがコンシューマハードウェアでインタラクティブな速度で動きます。ChromeはGemini Nanoをデバイス内蔵で提供し、Transformers.jsのようなライブラリのおかげで開発体験も驚くほどスムーズです。
この記事では、ブラウザでLLMを動かすために必要なすべてをカバーします。技術スタック、モデル選択と量子化、実ハードウェアでのパフォーマンスベンチマーク、Chrome内蔵AI API、そしてクライアントサイドAI機能をプロダクションに出すためのパターンまで。すぐに使えるTypeScriptコード付きです。
なぜブラウザでAIを動かすのか?
実装に入る前に、クライアントサイドAIが適切な場面とそうでない場面を整理しましょう。
ブラウザAIのメリット
推論ごとの追加コストゼロ。 モデルをダウンロードすれば、以降のすべての推論は無料です。ユーザーあたりのクエリ量が多い機能(自動補完、文法チェック、コード提案)では、APIコールと比べて単位経済が圧倒的に有利です。
アーキテクチャレベルのプライバシー。 ユーザーデータがデバイスを離れません。プライバシーポリシーの曲芸も、GDPRのデータ転送問題も、学習データ漏洩のリスクもなし。医療、法務、個人の日記など、センシティブな分野ではあると嬉しいではなく必須です。
100ms以下のレイテンシ。 ネットワーク往復がないため、小さなモデルではほぼ瞬時のレスポンスが可能です。自動補完、インライン提案、リアルタイム分類が一瞬に感じられます。
オフライン対応。 モデルがキャッシュされれば、ネットワーク接続なしで動作します。飛行機の中でもAI機能が使えるPWA——これは本当の差別化ポイントです。
レートリミットなし。 APIクォータも、スロットリングも、Hacker Newsに載った深夜3時の「429 Too Many Requests」もありません。
サーバーサイドAIがまだ優れている場面
大型モデルの能力。 GPT-4クラスの推論にはまだ100B以上のパラメータモデルが必要で、ブラウザには収まりません。複雑な推論、マルチステップエージェント、大きなコンテキストウィンドウにはAPIコールが必要です。
初回ロード体験。 モデルのダウンロード(500MB〜2GB)で初回利用時に大きな待ち時間が発生します。遅い回線のユーザーは最初の推論まで数分待つことになります。
モバイルバッテリーの制約。 モバイルデバイスでGPU推論を実行すると、バッテリーが激しく消耗します。重い推論ワークロードはモバイルユーザー向けにサーバーサイド処理が必要です。
一貫性の保証。 GPU、ドライバー、量子化の違いで出力がわずかに異なります。再現可能で決定論的な結果が必要なら、サーバーサイド推論の方が制御しやすいです。
2026年の最適解:高頻度・低複雑度のタスクにブラウザAI(自動補完、分類、短文要約、エンベディング)、重い推論にサーバーAI(マルチステップエージェント、長文生成、複雑な分析)。
技術スタック
2026年のブラウザAIを支える3本柱:
1. WebGPU — パフォーマンスの骨格
WebGPUは、WebGLに代わるモダンなWeb向けGPU APIです。グラフィックス用に設計されたWebGLとは異なり、WebGPUはコンピュートワークロード向けに作られました——まさにニューラルネットワーク推論が必要とするものです。
// WebGPUサポートをチェック async function checkWebGPU(): Promise<GPUDevice | null> { if (!navigator.gpu) { console.warn('このブラウザはWebGPUをサポートしていません'); return null; } const adapter = await navigator.gpu.requestAdapter(); if (!adapter) { console.warn('GPUアダプターが見つかりません'); return null; } const device = await adapter.requestDevice(); // GPU情報を出力 const info = await adapter.requestAdapterInfo(); console.log(`GPU: ${info.vendor} ${info.device}`); console.log(`最大バッファサイズ: ${device.limits.maxBufferSize / 1024 / 1024}MB`); console.log(`最大コンピュートワークグループサイズ: ${device.limits.maxComputeWorkgroupSizeX}`); return device; }
ブラウザサポート状況(2026年3月):
| ブラウザ | WebGPU状態 | 備考 |
|---|---|---|
| Chrome 113+ | ✅ 安定 | 2023年4月から正式サポート |
| Edge 113+ | ✅ 安定 | Chromiumベース、Chromeと同じ |
| Firefox 147+ | ✅ 安定 | 2026年1月よりデフォルト有効(Win/macOS) |
| Safari 26+ | ✅ 安定 | macOS/iOS/iPadOS完全WebGPUサポート |
| Mobile Chrome | ⚠️ Androidのみ | フラッグシップGPU必要 (Adreno 730+) |
| iOS Safari 26+ | ✅ サポート | iOS 26+でWebGPU利用可能 |
行列積のWebGPU vs WebGLパフォーマンス(Transformerの核心演算):
| 演算 | WebGL | WebGPU | Speedup |
|---|---|---|---|
| MatMul 1024×1024 | 45ms | 8ms | 5.6× |
| MatMul 4096×4096 | 890ms | 95ms | 9.4× |
| Batch attention (8 heads) | 120ms | 18ms | 6.7× |
| 全順伝播 (125M params) | 340ms | 52ms | 6.5× |
この差は圧倒的です。WebGPUのコンピュートシェーダー、共有メモリ、ワークグループ同期が、ブラウザ内でリアルタイムLLM推論を現実にするパフォーマンスを解放します。
2. Transformers.js — 開発者フレンドリーな道
Transformers.js(Hugging Face製)は、おなじみのPython Transformers APIをJavaScriptに持ち込みます。内部的にはONNX Runtime Webを使い、WebGPUに処理を委譲して高速化しています。
import { pipeline, env } from '@huggingface/transformers'; // ブラウザ環境用に設定 env.allowLocalModels = false; env.useBrowserCache = true; // テキスト生成——完全にクライアントサイドで実行 const generator = await pipeline('text-generation', 'onnx-community/Qwen2.5-0.5B-Instruct', { device: 'webgpu', dtype: 'q4', // 4ビット量子化 } ); const output = await generator('Explain WebGPU in one paragraph:', { max_new_tokens: 150, temperature: 0.7, do_sample: true, }); console.log(output[0].generated_text);
Transformers.js v3の主な機能:
- WebGPUデバイスターゲティング (
device: 'webgpu') - 内蔵量子化サポート (
dtype: 'q4','q4f16','fp16') - チャットUI向けストリーミングトークン生成
- Hugging Faceに1,200以上の事前変換済みONNXモデル
- ブラウザCache APIでのモデルキャッシュ(セッション間で永続)
- Web Workerによるノンブロッキング推論
3. ONNX Runtime Web — 推論エンジン
ONNX Runtime WebはTransformers.jsの下にあるエンジンです。より低レベルな制御が必要な場合やカスタムONNXモデルがある場合は、直接使えます:
import * as ort from 'onnxruntime-web/webgpu'; async function runInference(modelPath: string, inputText: string) { // WebGPU実行プロバイダーでセッション作成 const session = await ort.InferenceSession.create(modelPath, { executionProviders: ['webgpu'], graphOptimizationLevel: 'all', }); // 入力テンソルを準備 const inputIds = tokenize(inputText); // トークナイザー const tensor = new ort.Tensor('int64', BigInt64Array.from(inputIds.map(BigInt)), [1, inputIds.length] ); // 推論実行 const results = await session.run({ input_ids: tensor }); return results.logits; }
ONNX Runtime直接利用 vs Transformers.js、いつ使い分ける?
| シナリオ | Transformers.js | ONNX Runtime直接利用 |
|---|---|---|
| 標準NLPタスク | ✅ ハイレベルAPI | オーバーキル |
| カスタムファインチューニングモデル | ONNX変換済みなら | ✅ 完全な制御 |
| テキスト以外のモダリティ(音声、画像) | ✅ サポート済みパイプライン | カスタムパイプライン用 |
| 最大パフォーマンスチューニング | 限定的な制御 | ✅ セッションオプション、グラフ最適化 |
| プロトタイプ速度 | ✅ 3行のコード | ボイラープレートが多い |
モデル選択:ブラウザで実際に何が動くのか?
これが核心の問いです。fp16の70Bパラメータモデルは140GBのVRAMが必要——ブラウザタブでは当然無理です。しかし、積極的な量子化を適用すれば、思っている以上に選択肢があります。
うまく動くモデル(2026年3月時点)
| Model | パラメータ | 量子化サイズ | tok/s (RTX 4070) | tok/s (M3 MacBook) | 最適な用途 |
|---|---|---|---|---|---|
| Qwen2.5-0.5B-Instruct | 0.5B | 350MB (Q4) | 85 | 45 | 分類、抽出 |
| Qwen2.5-1.5B-Instruct | 1.5B | 900MB (Q4) | 42 | 22 | 短文テキスト生成 |
| SmolLM2-1.7B-Instruct | 1.7B | 1.0GB (Q4) | 38 | 20 | 汎用チャット |
| Phi-3.5-mini-instruct | 3.8B | 2.1GB (Q4) | 18 | 9 | 推論タスク |
| Gemma-2-2B-Instruct | 2.0B | 1.2GB (Q4) | 28 | 14 | 指示追従 |
| Llama-3.2-1B-Instruct | 1.2B | 750MB (Q4) | 52 | 28 | 高速汎用 |
経験則: インタラクティブなブラウザUIでは秒速20トークン以上が必要です。一般的なハードウェアでは2Bパラメータ以下のモデルに限られます。3B以上も動きますが、リアルタイムチャットではもっさり感があります。
量子化:サイズと速度のトレードオフ
量子化はモデルの精度を32ビット浮動小数点からより小さな表現に減らします。各オプションの意味:
fp32 (32-bit) → fp16 (16-bit) → int8 (8-bit) → int4 (4-bit)
フルサイズ → 半分 → 1/4 → 1/8
最高品質 → → → 最速/最小
品質への影響(Qwen2.5-1.5BのMMLUベンチマーク):
| 精度 | モデルサイズ | MMLUスコア | トークン/秒 | メモリ使用量 |
|---|---|---|---|---|
| fp16 | 3.0 GB | 61.8 | 12 | 3.4 GB |
| int8 | 1.5 GB | 61.2 | 28 | 1.8 GB |
| int4 (Q4) | 900 MB | 59.1 | 42 | 1.2 GB |
| int4 (Q4_K_M) | 950 MB | 60.3 | 40 | 1.3 GB |
Q4_K_M混合量子化が最適解です——アテンション層は高精度を維持しつつ、フィードフォワード層を積極的に量子化することで、1/3のサイズで97%の品質を維持します。
プログレス表示付きモデルロード
ユーザーにダウンロードの進捗を見せる必要があります:
import { AutoTokenizer, AutoModelForCausalLM, TextStreamer } from '@huggingface/transformers'; interface LoadingProgress { status: 'downloading' | 'loading' | 'ready'; file?: string; progress?: number; loaded?: number; total?: number; } async function loadModel( modelId: string, onProgress: (progress: LoadingProgress) => void ): Promise<{ model: any; tokenizer: any }> { onProgress({ status: 'downloading' }); const tokenizer = await AutoTokenizer.from_pretrained(modelId, { progress_callback: (data: any) => { if (data.status === 'progress') { onProgress({ status: 'downloading', file: data.file, progress: data.progress, loaded: data.loaded, total: data.total, }); } }, }); const model = await AutoModelForCausalLM.from_pretrained(modelId, { device: 'webgpu', dtype: 'q4', progress_callback: (data: any) => { if (data.status === 'progress') { onProgress({ status: 'downloading', file: data.file, progress: data.progress, loaded: data.loaded, total: data.total, }); } }, }); onProgress({ status: 'ready' }); return { model, tokenizer }; }
Chrome内蔵AI API
Chrome 131+で実験的な内蔵AI APIが導入されました。ブラウザネイティブのAPIでGemini Nano(小型オンデバイスモデル)を使えます。モデルのダウンロード不要。ライブラリ不要。モデルはChrome自体に同梱されています。
Prompt API
// 利用可能性チェック const capabilities = await self.ai.languageModel.capabilities(); console.log(capabilities.available); // 'readily', 'after-download', 'no' if (capabilities.available !== 'no') { // セッション作成 const session = await self.ai.languageModel.create({ systemPrompt: 'You are a helpful coding assistant. Be concise.', temperature: 0.7, topK: 40, }); // シンプルプロンプト const result = await session.prompt('What is a closure in JavaScript?'); console.log(result); // ストリーミング const stream = session.promptStreaming('Explain WebGPU briefly.'); for await (const chunk of stream) { process.stdout.write(chunk); } // セッションが会話コンテキストを保持 const followUp = await session.prompt('Give me a code example.'); // クリーンアップ session.destroy(); }
Summarization API
const summarizer = await self.ai.summarizer.create({ type: 'tl;dr', // 'tl;dr', 'key-points', 'teaser', 'headline' length: 'medium', // 'short', 'medium', 'long' format: 'plain-text', // 'plain-text', 'markdown' }); const summary = await summarizer.summarize(longArticleText); console.log(summary);
Translation API
const translator = await self.ai.translator.create({ sourceLanguage: 'en', targetLanguage: 'ja', }); const translated = await translator.translate('Hello, world!'); console.log(translated); // こんにちは、世界!
Chrome内蔵AI vs Transformers.js:いつどちらを使うか
| 要因 | Chrome内蔵AI | Transformers.js |
|---|---|---|
| モデルDL | なし(Chromeに同梱) | 初回350MB〜2GB |
| セットアップ複雑度 | 3行のコード | npm install + config |
| モデル選択 | Gemini Nanoのみ | 1,200以上のモデル |
| ブラウザサポート | Chromeのみ | 全WebGPU対応ブラウザ |
| 品質(GPT-4比) | ~60% | モデルにより異なる (50-75%) |
| タスクの柔軟性 | テキスト、画像、音声(マルチモーダル) | テキスト、画像、音声、エンベディング |
| ファインチューニング | 不可能 | カスタムONNXモデル可 |
| オフライン | ✅ Chromeインストール後 | ✅ モデルキャッシュ後 |
推奨: 素早いプロトタイプやChrome専用機能にはChrome内蔵AIを。クロスブラウザ対応、特定モデル、テキスト以外のモダリティが必要な場合はTransformers.jsを使いましょう。
プロダクションパターン
パターン1:Web Worker隔離
メインスレッドで推論を実行してはいけません。GPUコンピュートがイベントループをブロックしてUIがフリーズします。
// ai-worker.ts — Web Workerで実行 import { pipeline } from '@huggingface/transformers'; let generator: any = null; self.onmessage = async (e: MessageEvent) => { const { type, payload } = e.data; switch (type) { case 'LOAD': { self.postMessage({ type: 'STATUS', status: 'loading' }); generator = await pipeline('text-generation', payload.model, { device: 'webgpu', dtype: 'q4', progress_callback: (progress: any) => { self.postMessage({ type: 'PROGRESS', progress }); }, }); self.postMessage({ type: 'STATUS', status: 'ready' }); break; } case 'GENERATE': { if (!generator) { self.postMessage({ type: 'ERROR', error: 'モデルが読み込まれていません' }); return; } const result = await generator(payload.prompt, { max_new_tokens: payload.maxTokens ?? 256, temperature: payload.temperature ?? 0.7, do_sample: true, }); self.postMessage({ type: 'RESULT', text: result[0].generated_text, }); break; } } }; // main.ts — アプリから使用 class BrowserAI { private worker: Worker; private pending = new Map<string, (value: any) => void>(); constructor() { this.worker = new Worker( new URL('./ai-worker.ts', import.meta.url), { type: 'module' } ); this.worker.onmessage = (e) => { // レスポンス処理 }; } async load(model: string): Promise<void> { this.worker.postMessage({ type: 'LOAD', payload: { model } }); // 'ready'状態を待機... } async generate(prompt: string, options = {}): Promise<string> { this.worker.postMessage({ type: 'GENERATE', payload: { prompt, ...options }, }); // 結果を待機... return ''; } }
パターン2:ストリーミングトークン生成
チャットUIでは、トークンが生成されるたびにストリーミングしましょう:
import { AutoTokenizer, AutoModelForCausalLM, TextStreamer } from '@huggingface/transformers'; async function* streamGenerate( model: any, tokenizer: any, prompt: string, maxTokens: number = 256, ): AsyncGenerator<string> { const inputs = tokenizer(prompt, { return_tensors: 'pt' }); // トークンをyieldするカスタムストリーマー const tokens: string[] = []; let resolveNext: ((value: string) => void) | null = null; const streamer = new TextStreamer(tokenizer, { skip_prompt: true, callback_function: (text: string) => { if (resolveNext) { resolveNext(text); resolveNext = null; } else { tokens.push(text); } }, }); // 生成開始(バックグラウンド実行) const generatePromise = model.generate({ ...inputs, max_new_tokens: maxTokens, temperature: 0.7, do_sample: true, streamer, }); // トークン到着時にyield while (true) { if (tokens.length > 0) { yield tokens.shift()!; } else { const token = await new Promise<string>((resolve) => { resolveNext = resolve; }); yield token; } // 生成完了をチェック // (簡略化——実装では完了シグナルが必要) } await generatePromise; } // Reactコンポーネントでの使用 function ChatMessage({ prompt }: { prompt: string }) { const [text, setText] = useState(''); useEffect(() => { (async () => { for await (const token of streamGenerate(model, tokenizer, prompt)) { setText(prev => prev + token); } })(); }, [prompt]); return <p>{text}</p>; }
パターン3:サーバーフォールバック付きグレースフルデグラデーション
すべてのユーザーがWebGPUを使えるわけではありません。フォールバックチェーンを作りましょう:
type AIBackend = 'webgpu' | 'wasm' | 'server'; async function detectBestBackend(): Promise<AIBackend> { // 1. WebGPUを試行 if (navigator.gpu) { const adapter = await navigator.gpu.requestAdapter(); if (adapter) { const info = await adapter.requestAdapterInfo(); // 最低限のGPU性能をチェック const device = await adapter.requestDevice(); if (device.limits.maxBufferSize >= 256 * 1024 * 1024) { return 'webgpu'; } } } // 2. WASMにフォールバック(CPU専用、遅いが汎用) if (typeof WebAssembly !== 'undefined') { return 'wasm'; } // 3. 最終手段:サーバーサイド return 'server'; } async function createAIClient(): Promise<AIClient> { const backend = await detectBestBackend(); switch (backend) { case 'webgpu': return new BrowserAIClient({ device: 'webgpu', model: 'onnx-community/Qwen2.5-0.5B-Instruct' }); case 'wasm': return new BrowserAIClient({ device: 'wasm', model: 'onnx-community/Qwen2.5-0.5B-Instruct', // WASMは5-10倍遅いがどこでも動く }); case 'server': return new ServerAIClient({ endpoint: '/api/ai/generate' }); } }
パターン4:スマートモデルキャッシュ
モデルは大きいです。再ダウンロードを避けるため適切にキャッシュしましょう:
class ModelCache { private cacheName = 'ai-models-v1'; async getCacheInfo(): Promise<{ models: string[]; totalSize: number; }> { const cache = await caches.open(this.cacheName); const keys = await cache.keys(); let totalSize = 0; const models: string[] = []; for (const request of keys) { const response = await cache.match(request); if (response) { const blob = await response.blob(); totalSize += blob.size; models.push(new URL(request.url).pathname); } } return { models, totalSize }; } async clearOldModels(maxCacheSizeMB: number = 2048): Promise<void> { const { totalSize } = await this.getCacheInfo(); if (totalSize > maxCacheSizeMB * 1024 * 1024) { // キャッシュをクリアしてアクティブモデルを再ダウンロード await caches.delete(this.cacheName); console.log(`Cleared model cache (was ${(totalSize / 1024 / 1024).toFixed(0)}MB)`); } } async isModelCached(modelId: string): Promise<boolean> { const cache = await caches.open(this.cacheName); const keys = await cache.keys(); return keys.some(k => k.url.includes(modelId)); } } // キャッシュ状態に応じたUI表示 async function initAI() { const cache = new ModelCache(); const isCached = await cache.isModelCached('Qwen2.5-0.5B-Instruct'); if (isCached) { // 即座にロード——モデルはダウンロード済み showStatus('キャッシュからAIモデルを読み込み中...'); // キャッシュから2-5秒 vs ダウンロード30-60秒 } else { // 初回ダウンロードが必要 showStatus('AIモデルをダウンロード中(350MB)...'); showProgressBar(); } }
今日使える実践的ユースケース
すべてのAIユースケースがブラウザで動くわけではありません。実用的なものを紹介します:
1. スマート自動補完
// 高速でローカルなテキスト入力自動補完 const completer = await pipeline('text-generation', 'onnx-community/Qwen2.5-0.5B-Instruct', { device: 'webgpu', dtype: 'q4' } ); async function autocomplete(partial: string): Promise<string[]> { const prompt = `Complete this sentence naturally: "${partial}"`; const results = await completer(prompt, { max_new_tokens: 30, num_return_sequences: 3, temperature: 0.8, do_sample: true, }); return results.map((r: any) => r.generated_text.replace(prompt, '').trim() ); }
2. クライアントサイドテキスト分類
// スパム検出、感情分析、コンテンツモデレーション——APIコール不要 const classifier = await pipeline('zero-shot-classification', 'Xenova/mobilebert-uncased-mnli', { device: 'webgpu' } ); async function classifyContent(text: string): Promise<{ label: string; score: number; }> { const result = await classifier(text, [ 'spam', 'legitimate', 'positive', 'negative', 'neutral', 'question', 'statement', ]); return { label: result.labels[0], score: result.scores[0], }; }
3. ローカルエンベディング検索
// 完全にクライアントサイドでエンベディング生成——ローカル検索に最適 const embedder = await pipeline('feature-extraction', 'Xenova/all-MiniLM-L6-v2', { device: 'webgpu' } ); async function embed(text: string): Promise<number[]> { const result = await embedder(text, { pooling: 'mean', normalize: true, }); return Array.from(result.data); } // APIコールなしでローカル検索インデックスを構築 async function localSearch( query: string, documents: string[] ): Promise<{ doc: string; score: number }[]> { const queryEmbedding = await embed(query); const docEmbeddings = await Promise.all(documents.map(embed)); return docEmbeddings .map((docEmb, i) => ({ doc: documents[i], score: cosineSimilarity(queryEmbedding, docEmb), })) .sort((a, b) => b.score - a.score); }
4. リアルタイム翻訳
// APIコール不要の翻訳——チャットアプリに最適 const translator = await pipeline('translation', 'Xenova/nllb-200-distilled-600M', { device: 'webgpu', dtype: 'q4' } ); async function translate( text: string, from: string, to: string ): Promise<string> { const result = await translator(text, { src_lang: from, tgt_lang: to, max_length: 512, }); return result[0].translation_text; }
パフォーマンス最適化
ウォームアップ推論
モデルロード後の最初の推論は常に最も遅い(WebGPUパイプラインのコンパイル)。ウォームアップを実行しましょう:
async function warmUp(model: any, tokenizer: any): Promise<void> { const dummyInput = tokenizer('warmup', { return_tensors: 'pt' }); await model.generate({ ...dummyInput, max_new_tokens: 1, }); // これで最初の実推論が2-3倍速くなる }
KVキャッシュ管理
マルチターン会話では、以前のトークンの再計算を避けるためKVキャッシュを管理しましょう:
interface ConversationState { pastKeyValues: any; tokenCount: number; } async function continueConversation( model: any, tokenizer: any, newMessage: string, state: ConversationState | null, ): Promise<{ response: string; newState: ConversationState }> { const inputs = tokenizer(newMessage, { return_tensors: 'pt' }); const generation = await model.generate({ ...inputs, max_new_tokens: 256, past_key_values: state?.pastKeyValues ?? null, // 前のターンのキャッシュされた計算を再利用 }); return { response: tokenizer.decode(generation[0], { skip_special_tokens: true }), newState: { pastKeyValues: generation.past_key_values, tokenCount: (state?.tokenCount ?? 0) + inputs.input_ids.length, }, }; }
メモリプレッシャーの監視
ブラウザはメモリを使いすぎたタブを強制終了します。監視して対応しましょう:
function monitorMemory(thresholdMB: number = 1500): void { if ('memory' in performance) { const memInfo = (performance as any).memory; const usedMB = memInfo.usedJSHeapSize / 1024 / 1024; const limitMB = memInfo.jsHeapSizeLimit / 1024 / 1024; console.log(`Memory: ${usedMB.toFixed(0)}MB / ${limitMB.toFixed(0)}MB`); if (usedMB > thresholdMB) { console.warn('メモリ使用量が高い——モデルのアンロードを検討'); // モデルのアンロードまたはバッチサイズの縮小 } } } // 定期的にチェック setInterval(() => monitorMemory(), 10000);
よくある落とし穴
落とし穴1:メインスレッドのブロック
最も多いミスです。WebGPUを使っていても、モデルの読み込みとトークナイゼーションはCPUで行われ、UIが数秒間フリーズすることがあります。
// ❌ NG: メインスレッドでロード const model = await pipeline('text-generation', 'model-id'); // ダウンロード+初期化中にUIがフリーズ // ✅ OK: Web Worker+進捗UI const worker = new Worker(new URL('./ai-worker.ts', import.meta.url)); worker.postMessage({ type: 'LOAD', model: 'model-id' }); // Workerの初期化中にローディングスピナーを表示
落とし穴2:モデルウォームアップの無視
WebGPUパイプラインのコンパイルにより、最初の推論は常に2〜5倍遅くなります。ユーザーはあなたのアプリのせいにします。
// ❌ NG: ユーザーの最初のプロンプトが遅い // ユーザーが入力して3秒待つ → 悪いUX // ✅ OK: ロード直後にウォームアップ await loadModel(); await warmUp(model, tokenizer); // GPUパイプラインを事前コンパイル // 最初のユーザープロンプトも一貫して高速
落とし穴3:非対応ブラウザへのフォールバックなし
Webユーザーの約15%がまだWebGPUをサポートしていません(古いブラウザ、一部のAndroid、ドライバー未更新のLinux)。
// ❌ NG: WebGPUが利用可能と想定 const model = await pipeline('text-generation', 'model', { device: 'webgpu' }); // 非対応ブラウザでクラッシュ // ✅ OK: プログレッシブエンハンスメント const backend = await detectBestBackend(); if (backend === 'server') { showMessage('お使いのブラウザではサーバーAIを使用します。Chromeにアップグレードすると、より高速でプライベートなAIを体験できます。'); }
落とし穴4:ページロード時のモデルダウンロード
ユーザーが求めていない900MBのダウンロードは敵対的なUXです。
// ❌ NG: ページロード時に自動ダウンロード window.onload = () => loadModel('900MB-model'); // ユーザーの帯域を消費し、モバイルデータプランを圧迫 // ✅ OK: ユーザーの明示的なアクションでオンデマンドロード document.getElementById('ai-btn')!.onclick = async () => { showConfirmation('AIモデルをダウンロードしますか?(900MB)今後のアクセスではキャッシュされます。'); // ユーザーの確認後にのみダウンロード };
意思決定フレームワーク
ブラウザでAIが必要か?
↓
高頻度・低複雑度か?
↓ はい ↓ いいえ
↓ → サーバーAPIを使用
プライバシーは重要か?
↓ はい ↓ いいえ
↓ → サーバーAPIを検討
↓ (よりシンプルで高性能)
↓
500MB〜2GBの初回ダウンロードを許容できるか?
↓ はい ↓ いいえ
↓ → Chrome内蔵AIを使用
↓ (zero download, Chromeのみ)
↓
Transformers.js + WebGPUを使用
↓
インタラクティブ速度なら2Bパラメータ以下のモデル
↓
Web Worker+サーバーフォールバックでデプロイ
まとめ
2026年のブラウザAIはリアルで、実用的で、プロダクション対応です——ただし条件付きで。サーバーサイドAIの代替ではなく、特定のユースケースで輝く補完レイヤーです。
最適なポジション:APIコールのコストが見合わない高頻度・プライバシー重視・レイテンシ重要なタスク。自動補完、分類、ローカル検索、コンテンツモデレーション、リアルタイム翻訳——これらはWebGPU上の2B以下モデルで見事に動きます。
アクションアイテム:
-
「ブラウザにAIを入れよう」ではなく、具体的なユースケースから始める。 ローカル推論が本当の問題を解決する機能を1つ選びましょう(コスト、プライバシー、レイテンシ)。
-
最初のモデルはQwen2.5-0.5BかLlama-3.2-1Bがおすすめ。 どちらも高速で、ほとんどのタスクに十分な性能があり、Q4量子化でブラウザメモリに余裕で収まります。
-
必ずWeb Workerを使う。 例外なし。メインスレッド推論はカクカクUIへの直行便です。
-
フォールバックチェーンを構築する。 WebGPU → WASM → サーバー。ユーザーのブラウザがWebGPUをサポートしていると決して仮定しないでください。
-
許可なくモデルをダウンロードしない。 サイズを示した明示的なオプトインは、基本的なUXマナーです。
「データセンターが必要なAI」と「ブラウザタブで動くAI」の間のギャップは急速に縮まっています。モデルはより小さく賢くなり、ランタイム(WebGPU)はより速くなり、ツール(Transformers.js)はよりスムーズになっています。適切なユースケースにおいて、クライアントサイドAIは未来ではなく——もう最良の選択肢です。
関連ツールを見る
Pockitの無料開発者ツールを試してみましょう