Como rodar LLMs no navegador: WebGPU, Transformers.js e as APIs de IA integrada do Chrome
Toda feature de IA que você shippa hoje faz uma API call. O usuário digita um prompt, seu server encaminha pra OpenAI, espera 800ms-3s, paga $0.01-0.50, e manda a resposta de volta. Pra um feature de chat com 10.000 DAU, são $3.000-15.000/mês só em custos de API — sem contar infraestrutura de servidor.
Mas e se o modelo rodasse no dispositivo do usuário? Sem API call. Sem latência além da computação. Sem custo por inferência. Sem dados do usuário saindo do navegador.
Isso não é mais hipotético. Em 2026, o navegador se tornou um runtime legítimo de inferência AI. WebGPU oferece acesso GPU quase nativo. Modelos quantizados abaixo de 2GB rodam a velocidades interativas em hardware consumer. O Chrome já vem com Gemini Nano on-device. E bibliotecas como Transformers.js tornaram a experiência de desenvolvimento surpreendentemente suave.
Este guia cobre tudo que você precisa pra rodar LLMs no navegador hoje: a stack tecnológica, seleção de modelos e quantização, benchmarks de performance em hardware real, as APIs de IA integrada do Chrome, e padrões de produção pra shippar features de IA client-side. Tudo com código TypeScript funcionando.
Por que rodar IA no navegador?
Antes de mergulhar na implementação, vamos deixar claro quando IA client-side faz sentido — e quando não faz.
O caso a favor da IA no navegador
Custo marginal zero por inferência. Uma vez que o modelo é baixado, toda inferência subsequente é grátis. Pra features com alto volume de queries por usuário (autocomplete, correção gramatical, sugestões de código), a economia unitária é dramaticamente melhor que API calls.
Privacidade por arquitetura. Os dados do usuário nunca saem do dispositivo. Sem malabarismos com políticas de privacidade, sem preocupações GDPR sobre transferência de dados, sem risco de vazamento de dados de treinamento. Pra domínios sensíveis (saúde, jurídico, diários pessoais), não é nice-to-have — é requisito.
Latência abaixo de 100ms. Sem round-trip de rede, as respostas podem ser quase instantâneas pra modelos pequenos. Autocomplete, sugestões inline e classificação real-time parecem instantâneas.
Funciona offline. Uma vez que o modelo está em cache, funciona sem conectividade. PWAs com features de IA que funcionam no avião — isso é um diferenciador real.
Sem rate limits. Sem cotas de API, sem throttling, sem "429 Too Many Requests" às 3 da manhã quando sua feature sai no Hacker News.
Quando IA server-side ainda é melhor
Capacidade de modelos grandes. Raciocínio nível GPT-4 ainda requer modelos de 100B+ parâmetros que não cabem num navegador. Pra raciocínio complexo, agentes multi-step ou janelas de contexto longas, API calls continuam necessárias.
Experiência do primeiro carregamento. Downloads de modelos (500MB-2GB) criam um atraso significativo no primeiro uso. Usuários com conexões lentas vão esperar minutos antes da primeira inferência.
Bateria no mobile. Rodar inferência GPU em dispositivos móveis drena a bateria agressivamente. Workloads pesados precisam de processamento server-side pra usuários mobile.
Garantias de consistência. GPUs, drivers e quantizações diferentes produzem outputs ligeiramente diferentes. Se você precisa de resultados reproduzíveis e determinísticos, inferência server-side oferece mais controle.
O sweet spot em 2026: use IA no navegador pra tarefas de alta frequência e baixa complexidade (autocomplete, classificação, resumo de textos curtos, embeddings) e IA no servidor pra raciocínio pesado (agentes multi-step, geração longa, análise complexa).
A stack tecnológica
Três pilares tornam a IA no navegador possível em 2026:
1. WebGPU — A espinha dorsal de performance
WebGPU substitui o WebGL como a API GPU moderna pra web. Diferente do WebGL (projetado pra gráficos), WebGPU foi construído pra workloads de computação — exatamente o que inferência de redes neurais precisa.
// Verificar suporte WebGPU async function checkWebGPU(): Promise<GPUDevice | null> { if (!navigator.gpu) { console.warn('WebGPU não suportado neste navegador'); return null; } const adapter = await navigator.gpu.requestAdapter(); if (!adapter) { console.warn('Nenhum adaptador GPU encontrado'); return null; } const device = await adapter.requestDevice(); // Info da GPU const info = await adapter.requestAdapterInfo(); console.log(`GPU: ${info.vendor} ${info.device}`); console.log(`Max buffer size: ${device.limits.maxBufferSize / 1024 / 1024}MB`); console.log(`Max compute workgroup size: ${device.limits.maxComputeWorkgroupSizeX}`); return device; }
Suporte dos navegadores (março 2026):
| Navegador | Status WebGPU | Notas |
|---|---|---|
| Chrome 113+ | ✅ Estável | Suporte completo desde abril 2023 |
| Edge 113+ | ✅ Estável | Baseado em Chromium, igual ao Chrome |
| Firefox 147+ | ✅ Estável | Habilitado por padrão desde jan 2026 (Win/macOS) |
| Safari 26+ | ✅ Estável | Suporte completo WebGPU em macOS/iOS/iPadOS |
| Mobile Chrome | ⚠️ Só Android | Requer GPU flagship (Adreno 730+) |
| iOS Safari 26+ | ✅ Suportado | WebGPU disponível no iOS 26+ |
WebGPU vs WebGL performance pra multiplicação de matrizes (crítico pra transformers):
| Operação | 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× |
| Forward pass completo (125M params) | 340ms | 52ms | 6.5× |
O salto é massivo. Os compute shaders do WebGPU, memória compartilhada e sincronização de workgroups desbloqueiam a performance que torna a inferência LLM em tempo real viável no navegador.
2. Transformers.js — O caminho developer-friendly
Transformers.js (da Hugging Face) traz a familiar API Transformers de Python pro JavaScript. Por baixo dos panos, usa ONNX Runtime Web, que delega pro WebGPU pra aceleração.
import { pipeline, env } from '@huggingface/transformers'; // Configurar pro navegador env.allowLocalModels = false; env.useBrowserCache = true; // Geração de texto — roda inteiramente client-side const generator = await pipeline('text-generation', 'onnx-community/Qwen2.5-0.5B-Instruct', { device: 'webgpu', dtype: 'q4', // quantização 4-bit } ); 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);
Features chave do Transformers.js v3:
- Targeting de device WebGPU (
device: 'webgpu') - Suporte a quantização built-in (
dtype: 'q4','q4f16','fp16') - Geração de tokens streaming pra chat UIs
- 1.200+ modelos ONNX pré-convertidos no Hugging Face
- Cache de modelos na Cache API do navegador (persiste entre sessões)
- Suporte a Web Workers pra inferência non-blocking
3. ONNX Runtime Web — O motor de inferência
ONNX Runtime Web é o motor por baixo do Transformers.js. Se você precisa de controle mais baixo nível ou tem modelos ONNX custom, pode usar diretamente:
import * as ort from 'onnxruntime-web/webgpu'; async function runInference(modelPath: string, inputText: string) { // Criar sessão com execution provider WebGPU const session = await ort.InferenceSession.create(modelPath, { executionProviders: ['webgpu'], graphOptimizationLevel: 'all', }); // Preparar tensor de entrada const inputIds = tokenize(inputText); // Seu tokenizer const tensor = new ort.Tensor('int64', BigInt64Array.from(inputIds.map(BigInt)), [1, inputIds.length] ); // Rodar inferência const results = await session.run({ input_ids: tensor }); return results.logits; }
Quando usar ONNX Runtime direto vs Transformers.js:
| Cenário | Usar Transformers.js | Usar ONNX Runtime direto |
|---|---|---|
| Tarefas NLP padrão | ✅ API de alto nível | Overkill |
| Modelos custom fine-tuned | Se já forem ONNX | ✅ Controle total |
| Modalidades não-texto (áudio, visão) | ✅ Pipelines suportados | Pra pipelines custom |
| Tuning máximo de performance | Controle limitado | ✅ Opções de sessão, otimização de grafo |
| Velocidade de prototipagem | ✅ 3 linhas de código | Mais boilerplate |
Seleção de modelos: o que realmente roda num navegador?
Essa é a pergunta crítica. Um modelo de 70B parâmetros em fp16 precisa de 140GB de VRAM — obviamente não cabe numa aba do navegador. Mas com quantização agressiva, você tem mais opções do que imagina.
Modelos que funcionam bem (março 2026)
| Model | Params | Tamanho quantizado | tok/s (RTX 4070) | tok/s (M3 MacBook) | Melhor pra |
|---|---|---|---|---|---|
| Qwen2.5-0.5B-Instruct | 0.5B | 350MB (Q4) | 85 | 45 | Classificação, extração |
| Qwen2.5-1.5B-Instruct | 1.5B | 900MB (Q4) | 42 | 22 | Geração de texto curto |
| SmolLM2-1.7B-Instruct | 1.7B | 1.0GB (Q4) | 38 | 20 | Chat geral |
| Phi-3.5-mini-instruct | 3.8B | 2.1GB (Q4) | 18 | 9 | Tarefas de raciocínio |
| Gemma-2-2B-Instruct | 2.0B | 1.2GB (Q4) | 28 | 14 | Seguimento de instruções |
| Llama-3.2-1B-Instruct | 1.2B | 750MB (Q4) | 52 | 28 | Uso geral rápido |
Regra geral: Pra UIs de navegador interativas, você quer >20 tokens/segundo. Isso limita a modelos de ≤2B parâmetros em hardware mainstream. Modelos de 3B+ funcionam mas parecem lentos pra chat real-time.
Quantização: trocando tamanho por velocidade
Quantização reduz a precisão do modelo de floats de 32 bits pra representações menores. Veja o que as opções significam:
fp32 (32-bit) → fp16 (16-bit) → int8 (8-bit) → int4 (4-bit)
Tamanho full → Metade → Quarto → Oitavo
Melhor qualidade → → → Mais rápido/menor
Impacto na qualidade (medido no MMLU pra Qwen2.5-1.5B):
| Precisão | Tamanho | Score MMLU | Tokens/seg | Memória |
|---|---|---|---|---|
| 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 |
A quantização mista Q4_K_M é o sweet spot — mantém as camadas de atenção em maior precisão enquanto quantiza agressivamente as camadas feed-forward, preservando 97% da qualidade a 1/3 do tamanho.
Carregando modelos com tracking de progresso
Usuários precisam ver o progresso do download:
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 }; }
APIs de IA integrada do Chrome
Chrome 131+ introduziu APIs experimentais de IA integrada que permitem usar o Gemini Nano (um modelo on-device pequeno) através de APIs nativas do navegador. Sem download de modelos. Sem bibliotecas. O modelo já vem com o Chrome.
A Prompt API
// Verificar disponibilidade const capabilities = await self.ai.languageModel.capabilities(); console.log(capabilities.available); // 'readily', 'after-download', 'no' if (capabilities.available !== 'no') { // Criar uma sessão const session = await self.ai.languageModel.create({ systemPrompt: 'You are a helpful coding assistant. Be concise.', temperature: 0.7, topK: 40, }); // Prompt simples const result = await session.prompt('What is a closure in JavaScript?'); console.log(result); // Streaming const stream = session.promptStreaming('Explain WebGPU briefly.'); for await (const chunk of stream) { process.stdout.write(chunk); } // A sessão mantém contexto de conversa const followUp = await session.prompt('Give me a code example.'); // Limpar session.destroy(); }
A 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);
A Translation API
const translator = await self.ai.translator.create({ sourceLanguage: 'en', targetLanguage: 'ja', }); const translated = await translator.translate('Hello, world!'); console.log(translated); // こんにちは、世界!
IA integrada do Chrome vs Transformers.js: quando usar qual
| Fator | IA integrada Chrome | Transformers.js |
|---|---|---|
| Download do modelo | Nenhum (vem com o Chrome) | 350MB-2GB primeiro carregamento |
| Complexidade de setup | 3 linhas de código | npm install + config |
| Escolha de modelo | Só Gemini Nano | 1.200+ modelos |
| Suporte de navegadores | Só Chrome | Todos os navegadores com WebGPU |
| Qualidade (vs GPT-4) | ~60% | Varia por modelo (50-75%) |
| Flexibilidade de tarefas | Texto, imagem, áudio (multimodal) | Texto, visão, áudio, embeddings |
| Fine-tuning | Não é possível | Modelos ONNX custom |
| Offline | ✅ Depois de instalar o Chrome | ✅ Depois de cachear o modelo |
Recomendação: Use a IA integrada do Chrome pra protótipos rápidos e features Chrome-only. Use Transformers.js quando precisar de suporte cross-browser, modelos específicos ou modalidades não-texto.
Padrões de produção
Padrão 1: Isolamento com Web Worker
Nunca rode inferência na thread principal. Compute GPU bloqueia o event loop e congela sua UI.
// ai-worker.ts — rodar num 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: 'Modelo não carregado' }); 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 — usar na sua app 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) => { // Tratar respostas }; } async load(model: string): Promise<void> { this.worker.postMessage({ type: 'LOAD', payload: { model } }); // Aguardar status 'ready'... } async generate(prompt: string, options = {}): Promise<string> { this.worker.postMessage({ type: 'GENERATE', payload: { prompt, ...options }, }); // Aguardar resultado... return ''; } }
Padrão 2: Geração de tokens streaming
Pra chat UIs, faça stream dos tokens conforme são gerados:
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' }); // Streamer custom que faz yield de tokens 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); } }, }); // Iniciar geração (roda em background) const generatePromise = model.generate({ ...inputs, max_new_tokens: maxTokens, temperature: 0.7, do_sample: true, streamer, }); // Yield de tokens conforme chegam while (true) { if (tokens.length > 0) { yield tokens.shift()!; } else { const token = await new Promise<string>((resolve) => { resolveNext = resolve; }); yield token; } // Verificar se a geração completou // (simplificado — implementação real precisa de sinal de done) } await generatePromise; } // Uso num componente 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>; }
Padrão 3: Degradação elegante com fallback pra servidor
Nem todo usuário tem WebGPU. Monte uma cadeia de fallback:
type AIBackend = 'webgpu' | 'wasm' | 'server'; async function detectBestBackend(): Promise<AIBackend> { // 1. Tentar WebGPU if (navigator.gpu) { const adapter = await navigator.gpu.requestAdapter(); if (adapter) { const info = await adapter.requestAdapterInfo(); // Verificar capacidade mínima da GPU const device = await adapter.requestDevice(); if (device.limits.maxBufferSize >= 256 * 1024 * 1024) { return 'webgpu'; } } } // 2. Fallback pra WASM (só CPU, mais lento mas universal) if (typeof WebAssembly !== 'undefined') { return 'wasm'; } // 3. Último recurso: server-side 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-10x mais lento mas funciona em qualquer lugar }); case 'server': return new ServerAIClient({ endpoint: '/api/ai/generate' }); } }
Padrão 4: Cache inteligente de modelos
Modelos são grandes. Faça cache corretamente pra evitar re-downloads:
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) { // Limpar cache e re-baixar modelo ativo 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)); } } // Mostrar UI baseada no status do cache async function initAI() { const cache = new ModelCache(); const isCached = await cache.isModelCached('Qwen2.5-0.5B-Instruct'); if (isCached) { // Carga instantânea — modelo já baixado showStatus('Carregando modelo AI do cache...'); // Carrega em 2-5 segundos do cache vs 30-60s download } else { // Primeiro download necessário showStatus('Baixando modelo AI (350MB)...'); showProgressBar(); } }
Casos de uso práticos que funcionam hoje
Nem todo caso de uso de IA funciona no navegador. Esses são os que sim:
1. Autocomplete inteligente
// Autocomplete local rápido pra inputs de texto 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. Classificação de texto client-side
// Detecção de spam, análise de sentimento, moderação — sem API calls 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. Embeddings locais pra busca
// Embeddings inteiramente client-side — ótimo pra busca local 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); } // Montar um índice de busca local sem API calls 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. Tradução em tempo real
// Tradução sem API calls — perfeita pra apps de chat 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; }
Otimização de performance
Warm-up de inferência
A primeira inferência depois de carregar o modelo sempre é a mais lenta (compilação do pipeline WebGPU). Faça warm-up:
async function warmUp(model: any, tokenizer: any): Promise<void> { const dummyInput = tokenizer('warmup', { return_tensors: 'pt' }); await model.generate({ ...dummyInput, max_new_tokens: 1, }); // Primeira inferência real vai ser 2-3x mais rápida }
Gestão de KV cache
Pra conversas multi-turno, gerencie o KV cache pra evitar recomputar tokens anteriores:
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, // Reutiliza computação cacheada de turnos anteriores }); return { response: tokenizer.decode(generation[0], { skip_special_tokens: true }), newState: { pastKeyValues: generation.past_key_values, tokenCount: (state?.tokenCount ?? 0) + inputs.input_ids.length, }, }; }
Monitoramento de pressão de memória
Navegadores matam abas que usam muita memória. Monitore e responda:
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('Uso de memória alto — considere descarregar o modelo'); // Descarregar modelo ou reduzir batch size } } } // Verificar periodicamente setInterval(() => monitorMemory(), 10000);
Armadilhas comuns
Armadilha 1: Bloquear a thread principal
O erro mais comum. Mesmo com WebGPU, carregamento de modelo e tokenização acontecem na CPU e podem congelar sua UI por segundos.
// ❌ Ruim: carregar na thread principal const model = await pipeline('text-generation', 'model-id'); // UI congelada durante download + inicialização // ✅ Bom: Web Worker + UI de progresso const worker = new Worker(new URL('./ai-worker.ts', import.meta.url)); worker.postMessage({ type: 'LOAD', model: 'model-id' }); // Mostrar spinner de loading enquanto o worker inicializa
Armadilha 2: Ignorar warm-up do modelo
Primeira inferência é sempre 2-5x mais lenta pela compilação do pipeline WebGPU. O usuário culpa seu app.
// ❌ Ruim: primeiro prompt do usuário com resposta lenta // Usuário digita, espera 3 segundos → UX ruim // ✅ Bom: warm up imediato depois de carregar await loadModel(); await warmUp(model, tokenizer); // Pré-compilar pipelines GPU // Primeiro prompt do usuário é consistentemente rápido
Armadilha 3: Sem fallback pra navegadores não suportados
~15% dos usuários web ainda não têm suporte WebGPU (navegadores antigos, algumas versões de Android, Linux sem drivers atualizados).
// ❌ Ruim: assumir que WebGPU está disponível const model = await pipeline('text-generation', 'model', { device: 'webgpu' }); // Crasha em navegadores não suportados // ✅ Bom: melhoria progressiva const backend = await detectBestBackend(); if (backend === 'server') { showMessage('Features de IA usam nosso servidor pro seu navegador. Atualize pro Chrome pra IA mais rápida e privada.'); }
Armadilha 4: Baixar modelos no carregamento da página
Um download de 900MB que o usuário não pediu é UX hostil.
// ❌ Ruim: auto-download no carregamento da página window.onload = () => loadModel('900MB-model'); // Banda destruída, plano de dados mobile drenado // ✅ Bom: carregar on demand com ação explícita do usuário document.getElementById('ai-btn')!.onclick = async () => { showConfirmation('Baixar modelo AI (900MB)? Vai ficar em cache pras próximas visitas.'); // Só baixar depois que o usuário confirmar };
O framework de decisão
Você precisa de IA no navegador?
↓
É alta frequência, baixa complexidade?
↓ Sim ↓ Não
↓ → Use API de servidor
Privacidade é crítica?
↓ Sim ↓ Não
↓ → Considere API de servidor
↓ (mais simples, mais capaz)
↓
Dá pra tolerar 500MB-2GB de download inicial?
↓ Sim ↓ Não
↓ → Use IA integrada do Chrome
↓ (zero download, Só Chrome)
↓
Use Transformers.js + WebGPU
↓
Modelo ≤ 2B params pra velocidade interativa
↓
Deploy com Web Worker + fallback pra servidor
Conclusão
IA no navegador em 2026 é real, prática e pronta pra produção — com ressalvas. Não substitui IA server-side; é uma camada complementar que brilha em casos de uso específicos.
O sweet spot: tarefas de alta frequência, sensíveis a privacidade, críticas em latência onde o custo de API calls não fecha. Autocomplete, classificação, busca local, moderação de conteúdo, tradução real-time — tudo funciona lindo com modelos sub-2B rodando sobre WebGPU.
O que fazer:
-
Comece com um caso de uso específico, não "vamos colocar IA no navegador." Escolha a feature onde inferência local resolve um problema real (custo, privacidade, latência).
-
Comece com Qwen2.5-0.5B ou Llama-3.2-1B. Os dois são rápidos, capazes o suficiente pra maioria das tarefas, e cabem tranquilos na memória do navegador com quantização Q4.
-
Sempre use Web Workers. Sem exceções. Inferência na thread principal é caminho direto pra UI travada.
-
Monte a cadeia de fallback. WebGPU → WASM → Server. Nunca assuma que o navegador do usuário suporta WebGPU.
-
Não baixe modelos sem perguntar. Um opt-in explícito com indicação de tamanho é respeito básico de UX.
A distância entre "IA que precisa de um data center" e "IA que roda numa aba do navegador" está fechando rápido. Os modelos estão ficando menores e mais inteligentes. O runtime (WebGPU) está ficando mais rápido. O tooling (Transformers.js) está ficando mais suave. Pros casos de uso certos, IA client-side não é o futuro — já é a melhor opção.
Explore ferramentas relacionadas
Experimente estas ferramentas gratuitas do Pockit