Cómo correr LLMs en el navegador: WebGPU, Transformers.js y las APIs de IA integrada de Chrome
Cada feature de IA que shipeás hoy hace un API call. El usuario tipea un prompt, tu server lo reenvía a OpenAI, espera 800ms-3s, paga $0.01-0.50, y manda la respuesta de vuelta. Para un feature de chat con 10.000 DAU, eso son $3.000-15.000/mes solo en costos de API — sin contar infraestructura de servidor.
¿Pero qué pasa si el modelo corriera en el dispositivo del usuario? Sin API call. Sin latencia más allá de la computación. Sin costo por inferencia. Sin que los datos del usuario salgan del navegador.
Esto ya no es hipotético. En 2026, el navegador se convirtió en un runtime legítimo de inferencia AI. WebGPU provee acceso GPU casi nativo. Modelos cuantizados de menos de 2GB corren a velocidades interactivas en hardware consumer. Chrome viene con Gemini Nano on-device. Y librerías como Transformers.js hacen que la experiencia de desarrollo sea sorprendentemente suave.
Esta guía cubre todo lo que necesitás para correr LLMs en el navegador hoy: el stack tecnológico, selección de modelos y cuantización, benchmarks de performance en hardware real, las APIs de IA integrada de Chrome, y patrones de producción para shipear features de IA client-side. Todo con código TypeScript que funciona.
¿Por qué correr IA en el navegador?
Antes de meternos en la implementación, aclaremos cuándo la IA client-side tiene sentido — y cuándo no.
El caso a favor de la IA en el navegador
Cero costo marginal por inferencia. Una vez que el modelo se descarga, cada inferencia posterior es gratis. Para features con alto volumen de queries por usuario (autocomplete, corrección gramatical, sugerencias de código), la economía unitaria es dramáticamente mejor que API calls.
Privacidad por arquitectura. Los datos del usuario nunca salen del dispositivo. Sin malabares con políticas de privacidad, sin preocupaciones GDPR sobre transferencia de datos, sin riesgo de fuga de datos de entrenamiento. Para dominios sensibles (salud, legal, diarios personales), no es un nice-to-have — es un requisito.
Latencia sub-100ms. Sin round-trip de red, las respuestas pueden ser casi instantáneas para modelos chicos. Autocomplete, sugerencias inline y clasificación real-time se sienten instantáneas.
Funciona offline. Una vez que el modelo está cacheado, funciona sin conectividad. PWAs con features de IA que andan en un avión — eso es un diferenciador real.
Sin rate limits. Sin cuotas de API, sin throttling, sin "429 Too Many Requests" a las 3 AM cuando tu feature sale en Hacker News.
Cuándo la IA server-side sigue siendo mejor
Capacidad de modelos grandes. El razonamiento tipo GPT-4 todavía necesita modelos de 100B+ parámetros que no entran en un tab del navegador. Para razonamiento complejo, agentes multi-step o ventanas de contexto largas, los API calls siguen siendo necesarios.
Experiencia de primera carga. Las descargas de modelos (500MB-2GB) crean un delay significativo en el primer uso. Usuarios con conexiones lentas van a esperar minutos antes de su primera inferencia.
Batería en mobile. Correr inferencia GPU en dispositivos móviles drena la batería agresivamente. Cargas de trabajo pesadas necesitan procesamiento server-side para usuarios mobile.
Garantías de consistencia. Distintas GPUs, drivers y cuantizaciones producen outputs ligeramente diferentes. Si necesitás resultados reproducibles y determinísticos, la inferencia server-side ofrece más control.
El sweet spot en 2026: usá IA en el navegador para tareas de alta frecuencia y baja complejidad (autocomplete, clasificación, resumen de textos cortos, embeddings) y IA en servidor para razonamiento pesado (agentes multi-step, generación larga, análisis complejo).
El stack tecnológico
Tres pilares hacen posible la IA en el navegador en 2026:
1. WebGPU — La columna vertebral de performance
WebGPU reemplaza a WebGL como la API GPU moderna para la web. A diferencia de WebGL (diseñada para gráficos), WebGPU fue construida para cargas de trabajo de cómputo — exactamente lo que la inferencia de redes neuronales necesita.
// Verificar soporte de WebGPU async function checkWebGPU(): Promise<GPUDevice | null> { if (!navigator.gpu) { console.warn('WebGPU no soportado en este navegador'); return null; } const adapter = await navigator.gpu.requestAdapter(); if (!adapter) { console.warn('No se encontró adaptador GPU'); return null; } const device = await adapter.requestDevice(); // Info de GPU const info = await adapter.requestAdapterInfo(); console.log(`GPU: ${info.vendor} ${info.device}`); console.log(`Tamaño máximo de buffer: ${device.limits.maxBufferSize / 1024 / 1024}MB`); console.log(`Tamaño máximo de workgroup: ${device.limits.maxComputeWorkgroupSizeX}`); return device; }
Soporte de navegadores (marzo 2026):
| Navegador | Estado WebGPU | Notas |
|---|---|---|
| Chrome 113+ | ✅ Estable | Soporte completo desde abril 2023 |
| Edge 113+ | ✅ Estable | Basado en Chromium, igual que Chrome |
| Firefox 147+ | ✅ Estable | Habilitado por defecto desde enero 2026 (Win/macOS) |
| Safari 26+ | ✅ Estable | Soporte completo WebGPU en macOS/iOS/iPadOS |
| Mobile Chrome | ⚠️ Solo Android | Requiere GPU flagship (Adreno 730+) |
| iOS Safari 26+ | ✅ Soportado | WebGPU disponible en iOS 26+ |
WebGPU vs WebGL performance para multiplicación de matrices (crítico para transformers):
| Operación | 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× |
El salto es masivo. Los compute shaders de WebGPU, memoria compartida y sincronización de workgroups desbloquean la performance que hace viable la inferencia LLM en tiempo real en el navegador.
2. Transformers.js — El camino developer-friendly
Transformers.js (de Hugging Face) trae la familiar API de Transformers de Python a JavaScript. Por debajo usa ONNX Runtime Web, que delega a WebGPU para aceleración.
import { pipeline, env } from '@huggingface/transformers'; // Configurar para navegador env.allowLocalModels = false; env.useBrowserCache = true; // Generación de texto — corre enteramente client-side const generator = await pipeline('text-generation', 'onnx-community/Qwen2.5-0.5B-Instruct', { device: 'webgpu', dtype: 'q4', // cuantización 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 clave de Transformers.js v3:
- Targeting de device WebGPU (
device: 'webgpu') - Soporte de cuantización built-in (
dtype: 'q4','q4f16','fp16') - Generación de tokens streaming para chat UIs
- 1.200+ modelos ONNX pre-convertidos en Hugging Face
- Caching de modelos en Cache API del navegador (persiste entre sesiones)
- Soporte de Web Workers para inferencia non-blocking
3. ONNX Runtime Web — El motor de inferencia
ONNX Runtime Web es el motor debajo de Transformers.js. Si necesitás control de más bajo nivel o tenés modelos ONNX custom, podés usarlo directamente:
import * as ort from 'onnxruntime-web/webgpu'; async function runInference(modelPath: string, inputText: string) { // Crear sesión con execution provider WebGPU const session = await ort.InferenceSession.create(modelPath, { executionProviders: ['webgpu'], graphOptimizationLevel: 'all', }); // Preparar tensor de entrada const inputIds = tokenize(inputText); // Tu tokenizer const tensor = new ort.Tensor('int64', BigInt64Array.from(inputIds.map(BigInt)), [1, inputIds.length] ); // Correr inferencia const results = await session.run({ input_ids: tensor }); return results.logits; }
Cuándo usar ONNX Runtime directo vs Transformers.js:
| Escenario | Usar Transformers.js | Usar ONNX Runtime directo |
|---|---|---|
| Tareas NLP estándar | ✅ API de alto nivel | Overkill |
| Modelos custom fine-tuned | Si ya son ONNX | ✅ Control completo |
| Modalidades no-texto (audio, visión) | ✅ Pipelines soportados | Para pipelines custom |
| Tuning máximo de performance | Control limitado | ✅ Opciones de sesión, optimización de grafo |
| Velocidad de prototipado | ✅ 3 líneas de código | Más boilerplate |
Selección de modelos: ¿Qué corre realmente en un navegador?
Esta es la pregunta crítica. Un modelo de 70B parámetros en fp16 necesita 140GB de VRAM — obviamente no pasa en un tab del navegador. Pero con cuantización agresiva, tenés más opciones de las que pensás.
Modelos que andan bien (marzo 2026)
| Model | Params | Tamaño cuantizado | tok/s (RTX 4070) | tok/s (M3 MacBook) | Mejor para |
|---|---|---|---|---|---|
| Qwen2.5-0.5B-Instruct | 0.5B | 350MB (Q4) | 85 | 45 | Clasificación, extracción |
| Qwen2.5-1.5B-Instruct | 1.5B | 900MB (Q4) | 42 | 22 | Generación de texto corto |
| SmolLM2-1.7B-Instruct | 1.7B | 1.0GB (Q4) | 38 | 20 | Chat general |
| Phi-3.5-mini-instruct | 3.8B | 2.1GB (Q4) | 18 | 9 | Tareas de razonamiento |
| Gemma-2-2B-Instruct | 2.0B | 1.2GB (Q4) | 28 | 14 | Seguimiento de instrucciones |
| Llama-3.2-1B-Instruct | 1.2B | 750MB (Q4) | 52 | 28 | Uso general rápido |
Regla general: Para UIs de navegador interactivas, querés >20 tokens/segundo. Esto te limita a modelos de ≤2B parámetros en hardware mainstream. Modelos de 3B+ funcionan pero se sienten lentos para chat real-time.
Cuantización: Cambiando tamaño por velocidad
La cuantización reduce la precisión del modelo de floats de 32 bits a representaciones más chicas:
fp32 (32-bit) → fp16 (16-bit) → int8 (8-bit) → int4 (4-bit)
Tamaño full → Mitad → Cuarto → Octavo
Mejor calidad → → → Más rápido/chico
Impacto en calidad (medido en MMLU para Qwen2.5-1.5B):
| Precisión | Tamaño | Score MMLU | Tokens/seg | Memoria |
|---|---|---|---|---|
| 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 |
La cuantización mixta Q4_K_M es el sweet spot — mantiene las capas de atención a mayor precisión mientras cuantiza agresivamente las capas feed-forward, preservando el 97% de la calidad a 1/3 del tamaño.
Carga de modelos con tracking de progreso
Los usuarios necesitan ver el progreso de descarga:
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 de Chrome
Chrome 131+ introdujo APIs experimentales de IA integrada que te permiten usar Gemini Nano (un modelo on-device chico) a través de APIs nativas del navegador. Sin descarga de modelos. Sin librerías. El modelo viene con Chrome.
La Prompt API
// Verificar disponibilidad const capabilities = await self.ai.languageModel.capabilities(); console.log(capabilities.available); // 'readily', 'after-download', 'no' if (capabilities.available !== 'no') { // Crear una sesión const session = await self.ai.languageModel.create({ systemPrompt: 'You are a helpful coding assistant. Be concise.', temperature: 0.7, topK: 40, }); // Prompt simple 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); } // La sesión mantiene contexto de conversación const followUp = await session.prompt('Give me a code example.'); // Cleanup session.destroy(); }
La 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);
La 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 de Chrome vs Transformers.js: cuándo usar cuál
| Factor | IA integrada Chrome | Transformers.js |
|---|---|---|
| Descarga de modelo | Ninguna (viene con Chrome) | 350MB-2GB primera carga |
| Complejidad de setup | 3 líneas de código | npm install + config |
| Elección de modelo | Solo Gemini Nano | 1.200+ modelos |
| Soporte de navegadores | Solo Chrome | Todos los navegadores con WebGPU |
| Calidad (vs GPT-4) | ~60% | Varía por modelo (50-75%) |
| Flexibilidad de tareas | Texto, imagen, audio (multimodal) | Texto, visión, audio, embeddings |
| Fine-tuning | No es posible | Modelos ONNX custom |
| Offline | ✅ Después de instalar Chrome | ✅ Después de cachear modelo |
Recomendación: Usá la IA integrada de Chrome para prototipos rápidos y features Chrome-only. Usá Transformers.js cuando necesitás soporte cross-browser, modelos específicos o modalidades no-texto.
Patrones de producción
Patrón 1: Aislamiento con Web Worker
Nunca corras inferencia en el thread principal. El compute GPU bloquea el event loop y congela tu UI.
// ai-worker.ts — correr en un 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 no cargado' }); 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 desde tu 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) => { // Manejar respuestas }; } async load(model: string): Promise<void> { this.worker.postMessage({ type: 'LOAD', payload: { model } }); // Esperar estado 'ready'... } async generate(prompt: string, options = {}): Promise<string> { this.worker.postMessage({ type: 'GENERATE', payload: { prompt, ...options }, }); // Esperar resultado... return ''; } }
Patrón 2: Generación de tokens streaming
Para chat UIs, hacé stream de tokens a medida que se generan:
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 yield-ea 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 generación (corre en background) const generatePromise = model.generate({ ...inputs, max_new_tokens: maxTokens, temperature: 0.7, do_sample: true, streamer, }); // Yield de tokens a medida que llegan while (true) { if (tokens.length > 0) { yield tokens.shift()!; } else { const token = await new Promise<string>((resolve) => { resolveNext = resolve; }); yield token; } // Verificar si la generación terminó // (simplificado — la impl real necesita señal de done) } await generatePromise; } // Uso en un 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>; }
Patrón 3: Degradación elegante con fallback a servidor
No todos los usuarios tienen WebGPU. Armá una cadena de fallback:
type AIBackend = 'webgpu' | 'wasm' | 'server'; async function detectBestBackend(): Promise<AIBackend> { // 1. Intentar WebGPU if (navigator.gpu) { const adapter = await navigator.gpu.requestAdapter(); if (adapter) { const info = await adapter.requestAdapterInfo(); // Verificar capacidad mínima de GPU const device = await adapter.requestDevice(); if (device.limits.maxBufferSize >= 256 * 1024 * 1024) { return 'webgpu'; } } } // 2. Fallback a WASM (solo CPU, más lento pero 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 es 5-10x más lento pero funciona en todos lados }); case 'server': return new ServerAIClient({ endpoint: '/api/ai/generate' }); } }
Patrón 4: Caching inteligente de modelos
Los modelos son grandes. Cacheá adecuadamente para evitar re-descargas:
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) { // Limpiar cache y re-descargar modelo activo 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 basada en estado de cache async function initAI() { const cache = new ModelCache(); const isCached = await cache.isModelCached('Qwen2.5-0.5B-Instruct'); if (isCached) { // Carga instantánea — modelo ya descargado showStatus('Cargando modelo AI desde cache...'); // Carga en 2-5 segundos desde cache vs 30-60s descarga } else { // Primera descarga necesaria showStatus('Descargando modelo AI (350MB)...'); showProgressBar(); } }
Casos de uso prácticos que funcionan hoy
No todos los casos de uso de IA funcionan en el navegador. Estos son los que sí:
1. Autocomplete inteligente
// Autocomplete local rápido para 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. Clasificación de texto client-side
// Detección de spam, análisis de sentimiento, moderación — sin 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 locales para búsqueda
// Embeddings enteramente client-side — genial para búsqueda 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); } // Armar un índice de búsqueda local sin 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. Traducción en tiempo real
// Traducción sin API calls — perfecta para 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; }
Optimización de performance
Warm-up de inferencia
La primera inferencia después de cargar el modelo siempre es la más lenta (compilación del pipeline WebGPU). Hacé 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, }); // La primera inferencia real va a ser 2-3x más rápida }
Gestión de KV cache
Para conversaciones multi-turno, gestioná el KV cache para evitar recomputar tokens previos:
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 computación 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, }, }; }
Monitoreo de presión de memoria
Los navegadores matan tabs que usan mucha memoria. Monitoreá y respondé:
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 memoria alto — considerá descargar el modelo'); // Descargar modelo o reducir batch size } } } // Verificar periódicamente setInterval(() => monitorMemory(), 10000);
Errores comunes
Error 1: Bloquear el thread principal
El error más común. Incluso con WebGPU, la carga de modelos y la tokenización pasan en la CPU y pueden congelar tu UI por segundos.
// ❌ Mal: carga en el thread principal const model = await pipeline('text-generation', 'model-id'); // UI congelada durante descarga + inicialización // ✅ Bien: Web Worker + UI de progreso const worker = new Worker(new URL('./ai-worker.ts', import.meta.url)); worker.postMessage({ type: 'LOAD', model: 'model-id' }); // Mostrá spinner de carga mientras el worker inicializa
Error 2: Ignorar el warm-up del modelo
La primera inferencia siempre es 2-5x más lenta por la compilación del pipeline WebGPU. El usuario le echa la culpa a tu app.
// ❌ Mal: primer prompt del usuario con respuesta lenta // Usuario tipea, espera 3 segundos → mala UX // ✅ Bien: warm up inmediato después de cargar await loadModel(); await warmUp(model, tokenizer); // Pre-compilar pipelines GPU // El primer prompt del usuario es consistentemente rápido
Error 3: Sin fallback para navegadores no soportados
~15% de los usuarios web todavía no tienen soporte WebGPU (navegadores viejos, algunas versiones de Android, Linux sin drivers actualizados).
// ❌ Mal: asumir que WebGPU está disponible const model = await pipeline('text-generation', 'model', { device: 'webgpu' }); // Crashea en navegadores no soportados // ✅ Bien: mejora progresiva const backend = await detectBestBackend(); if (backend === 'server') { showMessage('Las features de IA usan nuestro servidor para tu navegador. Actualizá a Chrome para IA más rápida y privada.'); }
Error 4: Descargar modelos al cargar la página
Una descarga de 900MB que el usuario no pidió es UX hostil.
// ❌ Mal: auto-descargar al cargar la página window.onload = () => loadModel('900MB-model'); // Ancho de banda destruido, plan de datos mobile drenado // ✅ Bien: cargar on demand con acción explícita del usuario document.getElementById('ai-btn')!.onclick = async () => { showConfirmation('¿Descargar modelo AI (900MB)? Se va a cachear para futuras visitas.'); // Solo descargar después de que el usuario confirme };
El framework de decisión
¿Necesitás IA en el navegador?
↓
¿Es alta frecuencia, baja complejidad?
↓ Sí ↓ No
↓ → Usá API de servidor
¿Es crítica la privacidad?
↓ Sí ↓ No
↓ → Considerá API de servidor
↓ (más simple, más capaz)
↓
¿Podés tolerar 500MB-2GB de descarga inicial?
↓ Sí ↓ No
↓ → Usá IA integrada de Chrome
↓ (zero download, Solo Chrome)
↓
Usá Transformers.js + WebGPU
↓
Modelo ≤ 2B params para velocidad interactiva
↓
Deployeá con Web Worker + fallback a servidor
Conclusión
La IA en el navegador en 2026 es real, práctica y lista para producción — con salvedades. No reemplaza la IA server-side; es una capa complementaria que brilla en casos de uso específicos.
El sweet spot: tareas de alta frecuencia, sensibles a privacidad, críticas en latencia donde el costo de API calls no cierra. Autocomplete, clasificación, búsqueda local, moderación de contenido, traducción real-time — todo esto funciona hermoso con modelos sub-2B corriendo sobre WebGPU.
Lo que tenés que hacer:
-
Arrancá con un caso de uso específico, no "metámosle IA al navegador." Elegí el feature donde la inferencia local resuelve un problema real (costo, privacidad, latencia).
-
Empezá con Qwen2.5-0.5B o Llama-3.2-1B. Los dos son rápidos, suficientemente capaces para la mayoría de las tareas, y entran cómodos en la memoria del navegador con cuantización Q4.
-
Siempre usá Web Workers. Sin excepciones. Inferencia en el thread principal es un path directo a UI laggy.
-
Armá la cadena de fallback. WebGPU → WASM → Server. Nunca asumas que el navegador del usuario soporta WebGPU.
-
No descargues modelos sin preguntar. Un opt-in explícito con indicación de tamaño es respeto básico de UX.
La brecha entre "IA que necesita un data center" y "IA que corre en un tab del navegador" se está cerrando rápido. Los modelos se están haciendo más chicos y más inteligentes. El runtime (WebGPU) se está haciendo más rápido. El tooling (Transformers.js) se está haciendo más suave. Para los casos de uso correctos, la IA client-side no es el futuro — ya es la mejor opción.
Explora herramientas relacionadas
Prueba estas herramientas gratuitas de Pockit