Back

ブラウザで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の核心演算):

演算WebGLWebGPUSpeedup
MatMul 1024×102445ms8ms5.6×
MatMul 4096×4096890ms95ms9.4×
Batch attention (8 heads)120ms18ms6.7×
全順伝播 (125M params)340ms52ms6.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.jsONNX Runtime直接利用
標準NLPタスク✅ ハイレベルAPIオーバーキル
カスタムファインチューニングモデルONNX変換済みなら✅ 完全な制御
テキスト以外のモダリティ(音声、画像)✅ サポート済みパイプラインカスタムパイプライン用
最大パフォーマンスチューニング限定的な制御✅ セッションオプション、グラフ最適化
プロトタイプ速度✅ 3行のコードボイラープレートが多い

モデル選択:ブラウザで実際に何が動くのか?

これが核心の問いです。fp16の70Bパラメータモデルは140GBのVRAMが必要——ブラウザタブでは当然無理です。しかし、積極的な量子化を適用すれば、思っている以上に選択肢があります。

うまく動くモデル(2026年3月時点)

Modelパラメータ量子化サイズtok/s (RTX 4070)tok/s (M3 MacBook)最適な用途
Qwen2.5-0.5B-Instruct0.5B350MB (Q4)8545分類、抽出
Qwen2.5-1.5B-Instruct1.5B900MB (Q4)4222短文テキスト生成
SmolLM2-1.7B-Instruct1.7B1.0GB (Q4)3820汎用チャット
Phi-3.5-mini-instruct3.8B2.1GB (Q4)189推論タスク
Gemma-2-2B-Instruct2.0B1.2GB (Q4)2814指示追従
Llama-3.2-1B-Instruct1.2B750MB (Q4)5228高速汎用

経験則: インタラクティブなブラウザ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スコアトークン/秒メモリ使用量
fp163.0 GB61.8123.4 GB
int81.5 GB61.2281.8 GB
int4 (Q4)900 MB59.1421.2 GB
int4 (Q4_K_M)950 MB60.3401.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内蔵AITransformers.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以下モデルで見事に動きます。

アクションアイテム:

  1. 「ブラウザにAIを入れよう」ではなく、具体的なユースケースから始める。 ローカル推論が本当の問題を解決する機能を1つ選びましょう(コスト、プライバシー、レイテンシ)。

  2. 最初のモデルはQwen2.5-0.5BかLlama-3.2-1Bがおすすめ。 どちらも高速で、ほとんどのタスクに十分な性能があり、Q4量子化でブラウザメモリに余裕で収まります。

  3. 必ずWeb Workerを使う。 例外なし。メインスレッド推論はカクカクUIへの直行便です。

  4. フォールバックチェーンを構築する。 WebGPU → WASM → サーバー。ユーザーのブラウザがWebGPUをサポートしていると決して仮定しないでください。

  5. 許可なくモデルをダウンロードしない。 サイズを示した明示的なオプトインは、基本的なUXマナーです。

「データセンターが必要なAI」と「ブラウザタブで動くAI」の間のギャップは急速に縮まっています。モデルはより小さく賢くなり、ランタイム(WebGPU)はより速くなり、ツール(Transformers.js)はよりスムーズになっています。適切なユースケースにおいて、クライアントサイドAIは未来ではなく——もう最良の選択肢です。

AIWebGPULLMTransformers.jsONNXChromebrowseredge-AIprivacyTypeScript

関連ツールを見る

Pockitの無料開発者ツールを試してみましょう