Voltar para o Blog

WebGPU e Machine Learning no Browser em 2026: IA Rodando Localmente com JavaScript

Olá HaWkers, imagine rodar modelos de IA diretamente no seu browser, sem enviar dados para servidores, com performance próxima de aplicações nativas. Em 2026, isso não é mais ficção científica - é WebGPU.

Vamos explorar como essa tecnologia está transformando o que é possível fazer com JavaScript no browser.

O Que É WebGPU e Por Que Importa

WebGPU: O Sucessor do WebGL

// Evolução das APIs gráficas no browser

const graphicsAPIEvolution = {
  webgl1: {
    year: 2011,
    based_on: 'OpenGL ES 2.0',
    purpose: 'Gráficos 3D no browser',
    limitation: 'API antiga, sem compute shaders'
  },

  webgl2: {
    year: 2017,
    based_on: 'OpenGL ES 3.0',
    improvements: 'Transform feedback, instancing',
    limitation: 'Ainda baseada em API legacy'
  },

  webgpu: {
    year: '2023-2026 (adoção massiva)',
    based_on: 'Vulkan, Metal, DirectX 12',
    purpose: 'Gráficos E computação GPU',
    breakthrough: 'Compute shaders nativos = ML possível!'
  }
};

// Por que WebGPU é revolucionário para ML
const webgpuForML = {
  computeShaders: {
    what: 'Programas que rodam na GPU para cálculos paralelos',
    why: 'ML = milhões de operações matemáticas paralelas',
    benefit: 'GPU é 10-100x mais rápida que CPU para isso'
  },

  performance: {
    webgl: 'Hacks para simular compute com texturas',
    webgpu: 'Compute pipelines dedicados',
    improvement: '3-10x mais rápido que WebGL para ML'
  },

  memoryAccess: {
    webgl: 'Limitado, indireto via texturas',
    webgpu: 'Acesso direto a buffers de memória',
    impact: 'Modelos maiores, menos overhead'
  }
};

Suporte em Browsers (Janeiro 2026)

// Status de suporte WebGPU

const browserSupport2026 = {
  chrome: {
    status: 'Totalmente suportado',
    since: 'Chrome 113 (2023)',
    platforms: ['Windows', 'macOS', 'Linux', 'ChromeOS', 'Android']
  },

  edge: {
    status: 'Totalmente suportado',
    since: 'Edge 113 (2023)',
    platforms: ['Windows', 'macOS', 'Linux']
  },

  safari: {
    status: 'Suportado',
    since: 'Safari 17 (2023)',
    platforms: ['macOS Sonoma+', 'iOS 17+', 'visionOS']
  },

  firefox: {
    status: 'Suportado',
    since: 'Firefox 145 (2025)',
    platforms: ['macOS', 'Windows (em progresso)', 'Linux (em progresso)']
  },

  coverage2026: '~90% dos usuários desktop têm WebGPU'
};

// Detectando WebGPU
async function checkWebGPUSupport(): Promise<boolean> {
  if (!navigator.gpu) {
    console.log('WebGPU não suportado neste browser');
    return false;
  }

  const adapter = await navigator.gpu.requestAdapter();
  if (!adapter) {
    console.log('Nenhum adaptador GPU disponível');
    return false;
  }

  const device = await adapter.requestDevice();
  console.log('WebGPU disponível!', device.limits);
  return true;
}

Machine Learning com WebGPU na Prática

ONNX Runtime Web: O Padrão da Indústria

// ONNX Runtime Web com WebGPU

import * as ort from 'onnxruntime-web';

// Configurar para usar WebGPU
ort.env.wasm.numThreads = 4;

async function runInferenceWithWebGPU(imageData: ImageData) {
  // Criar sessão com WebGPU
  const session = await ort.InferenceSession.create(
    './models/resnet50.onnx',
    {
      executionProviders: ['webgpu'],  // Usar GPU!
      graphOptimizationLevel: 'all'
    }
  );

  // Preparar input
  const tensor = new ort.Tensor(
    'float32',
    preprocessImage(imageData),
    [1, 3, 224, 224]  // NCHW format
  );

  // Rodar inferência
  const startTime = performance.now();
  const results = await session.run({ input: tensor });
  const endTime = performance.now();

  console.log(`Inferência em ${endTime - startTime}ms`);

  // Processar output
  const predictions = results.output.data;
  return getTopKPredictions(predictions, 5);
}

function preprocessImage(imageData: ImageData): Float32Array {
  const { data, width, height } = imageData;
  const float32Data = new Float32Array(3 * 224 * 224);

  // Resize e normalize (simplificado)
  for (let c = 0; c < 3; c++) {
    for (let h = 0; h < 224; h++) {
      for (let w = 0; w < 224; w++) {
        const srcIdx = (h * width + w) * 4 + c;
        const dstIdx = c * 224 * 224 + h * 224 + w;
        // Normalize para ImageNet
        float32Data[dstIdx] = (data[srcIdx] / 255.0 - 0.485) / 0.229;
      }
    }
  }

  return float32Data;
}

Transformers.js: Hugging Face no Browser

// Transformers.js com WebGPU (2026)

import { pipeline, env } from '@xenova/transformers';

// Forçar uso de WebGPU
env.backends.onnx.wasm.proxy = false;
env.backends.onnx.wasm.numThreads = 4;

// Classificação de texto com BERT
async function classifyText(text: string) {
  const classifier = await pipeline(
    'sentiment-analysis',
    'Xenova/distilbert-base-uncased-finetuned-sst-2-english',
    { device: 'webgpu' }  // Usar GPU!
  );

  const result = await classifier(text);
  return result;
  // { label: 'POSITIVE', score: 0.9998 }
}

// Geração de texto com GPT-2
async function generateText(prompt: string) {
  const generator = await pipeline(
    'text-generation',
    'Xenova/gpt2',
    { device: 'webgpu' }
  );

  const result = await generator(prompt, {
    max_new_tokens: 50,
    temperature: 0.7,
    do_sample: true
  });

  return result[0].generated_text;
}

// Embeddings para busca semântica
async function createEmbeddings(texts: string[]) {
  const extractor = await pipeline(
    'feature-extraction',
    'Xenova/all-MiniLM-L6-v2',
    { device: 'webgpu' }
  );

  const embeddings = await extractor(texts, {
    pooling: 'mean',
    normalize: true
  });

  return embeddings.tolist();
}

Rodando LLMs Pequenos no Browser

// LLM local com WebLLM

import { CreateMLCEngine } from '@anthropic-ai/mlc-llm-web';

async function chatWithLocalLLM() {
  // Inicializar engine com modelo quantizado
  const engine = await CreateMLCEngine({
    model: 'Phi-2-q4f16_1',  // ~1.5GB quantizado
    device: 'webgpu',
    cacheUrl: 'indexeddb://llm-cache'
  });

  // Chat completions (API compatível com OpenAI)
  const response = await engine.chat.completions.create({
    messages: [
      { role: 'system', content: 'Você é um assistente útil.' },
      { role: 'user', content: 'Explique WebGPU em uma frase.' }
    ],
    temperature: 0.7,
    max_tokens: 100
  });

  console.log(response.choices[0].message.content);
  // "WebGPU é uma API moderna que permite acesso
  //  de baixo nível à GPU para gráficos e computação
  //  de alta performance no browser."
}

// Streaming de respostas
async function streamResponse(prompt: string) {
  const engine = await getEngine();  // Reutilizar engine

  const stream = await engine.chat.completions.create({
    messages: [{ role: 'user', content: prompt }],
    stream: true
  });

  for await (const chunk of stream) {
    const content = chunk.choices[0]?.delta?.content || '';
    process.stdout.write(content);  // Ou atualizar UI
  }
}

Casos de Uso Práticos

1. Classificação de Imagens em Tempo Real

// Classificador de imagens offline

class ImageClassifier {
  private session: ort.InferenceSession | null = null;
  private labels: string[] = [];

  async initialize() {
    // Carregar modelo e labels
    this.session = await ort.InferenceSession.create(
      './models/mobilenet_v3.onnx',
      { executionProviders: ['webgpu'] }
    );

    const response = await fetch('./models/imagenet_labels.json');
    this.labels = await response.json();
  }

  async classify(video: HTMLVideoElement): Promise<string> {
    if (!this.session) throw new Error('Não inicializado');

    // Capturar frame do vídeo
    const canvas = document.createElement('canvas');
    canvas.width = 224;
    canvas.height = 224;
    const ctx = canvas.getContext('2d')!;
    ctx.drawImage(video, 0, 0, 224, 224);

    const imageData = ctx.getImageData(0, 0, 224, 224);
    const tensor = this.imageToTensor(imageData);

    // Inferência
    const results = await this.session.run({ input: tensor });
    const probs = results.output.data as Float32Array;

    // Top prediction
    const maxIdx = probs.indexOf(Math.max(...probs));
    return this.labels[maxIdx];
  }

  private imageToTensor(imageData: ImageData): ort.Tensor {
    // ... preprocessamento
    return new ort.Tensor('float32', data, [1, 3, 224, 224]);
  }
}

// Uso com webcam
async function startWebcamClassification() {
  const classifier = new ImageClassifier();
  await classifier.initialize();

  const video = document.getElementById('webcam') as HTMLVideoElement;
  const stream = await navigator.mediaDevices.getUserMedia({ video: true });
  video.srcObject = stream;

  // Classificar a cada 100ms
  setInterval(async () => {
    const label = await classifier.classify(video);
    document.getElementById('result')!.textContent = label;
  }, 100);
}

2. Tradução Offline

// Tradução no browser sem servidor

import { pipeline } from '@xenova/transformers';

class OfflineTranslator {
  private translator: any = null;

  async initialize(sourceLang: string, targetLang: string) {
    const modelId = `Xenova/opus-mt-${sourceLang}-${targetLang}`;

    this.translator = await pipeline(
      'translation',
      modelId,
      {
        device: 'webgpu',
        // Cache no IndexedDB
        cache: { type: 'indexeddb' }
      }
    );
  }

  async translate(text: string): Promise<string> {
    if (!this.translator) throw new Error('Não inicializado');

    const result = await this.translator(text, {
      max_length: 512
    });

    return result[0].translation_text;
  }
}

// Uso
const translator = new OfflineTranslator();
await translator.initialize('en', 'pt');  // Inglês → Português

const translated = await translator.translate(
  'WebGPU enables machine learning in the browser'
);
console.log(translated);
// "WebGPU permite aprendizado de máquina no navegador"

3. Busca Semântica Local

// Busca semântica com embeddings locais

import { pipeline } from '@xenova/transformers';

class SemanticSearch {
  private embedder: any = null;
  private documents: string[] = [];
  private embeddings: number[][] = [];

  async initialize() {
    this.embedder = await pipeline(
      'feature-extraction',
      'Xenova/all-MiniLM-L6-v2',
      { device: 'webgpu' }
    );
  }

  async indexDocuments(docs: string[]) {
    this.documents = docs;

    // Gerar embeddings para todos os documentos
    const output = await this.embedder(docs, {
      pooling: 'mean',
      normalize: true
    });

    this.embeddings = output.tolist();
  }

  async search(query: string, topK: number = 5): Promise<SearchResult[]> {
    // Embedding da query
    const queryOutput = await this.embedder([query], {
      pooling: 'mean',
      normalize: true
    });
    const queryEmbedding = queryOutput.tolist()[0];

    // Calcular similaridade com todos os documentos
    const scores = this.embeddings.map((docEmb, idx) => ({
      index: idx,
      score: this.cosineSimilarity(queryEmbedding, docEmb)
    }));

    // Ordenar por similaridade
    scores.sort((a, b) => b.score - a.score);

    return scores.slice(0, topK).map(s => ({
      document: this.documents[s.index],
      score: s.score
    }));
  }

  private cosineSimilarity(a: number[], b: number[]): number {
    let dot = 0;
    for (let i = 0; i < a.length; i++) {
      dot += a[i] * b[i];
    }
    return dot;  // Vetores já normalizados
  }
}

interface SearchResult {
  document: string;
  score: number;
}

Performance e Limitações

Benchmarks Reais

// Comparação de performance (Janeiro 2026)

const performanceComparison = {
  imageClassification: {
    model: 'MobileNetV3',
    inputSize: '224x224',
    results: {
      cpu_wasm: '150ms',
      webgl: '45ms',
      webgpu: '12ms'  // 12x mais rápido que CPU!
    }
  },

  textGeneration: {
    model: 'Phi-2 (2.7B params, quantizado)',
    metric: 'tokens por segundo',
    results: {
      cpu_wasm: '2 tokens/s',
      webgpu: '25 tokens/s'  // Usável para chat!
    }
  },

  embeddings: {
    model: 'all-MiniLM-L6-v2',
    batchSize: 32,
    results: {
      cpu_wasm: '800ms',
      webgpu: '120ms'
    }
  }
};

Limitações Atuais

// O que ainda não funciona bem

const limitations2026 = {
  modelSize: {
    issue: 'Browsers têm limite de memória (~4GB)',
    impact: 'LLMs grandes (7B+) não funcionam bem',
    workaround: 'Use modelos quantizados (q4, q8)'
  },

  training: {
    issue: 'WebGPU é bom para inferência, não treinamento',
    impact: 'Fine-tuning pesado não é viável',
    workaround: 'Treinar server-side, inferir no browser'
  },

  coldStart: {
    issue: 'Primeiro carregamento baixa modelo inteiro',
    impact: 'Modelos grandes = espera inicial longa',
    workaround: 'Cache em IndexedDB, loading progressivo'
  },

  compatibility: {
    issue: 'Nem todos os browsers/devices suportam',
    impact: '~10% dos usuários sem WebGPU',
    workaround: 'Fallback para WASM/WebGL'
  }
};

// Implementando fallback gracioso
async function createInferenceSession(modelPath: string) {
  const providers = [];

  // Tentar WebGPU primeiro
  if (await checkWebGPUSupport()) {
    providers.push('webgpu');
  }

  // Fallback para WebGL
  providers.push('webgl');

  // Último recurso: WASM (CPU)
  providers.push('wasm');

  return ort.InferenceSession.create(modelPath, {
    executionProviders: providers
  });
}

Conclusão

WebGPU está tornando ML no browser uma realidade prática em 2026:

O que já funciona bem:

  • Classificação de imagens em tempo real
  • Embeddings e busca semântica
  • LLMs pequenos (até ~3B params quantizados)
  • Tradução e NLP offline
  • Detecção de objetos

O que ainda é desafiador:

  • LLMs muito grandes (7B+)
  • Treinamento de modelos
  • Usuários com hardware antigo

Benefícios chave:

  • Privacidade: Dados nunca saem do device
  • Latência: Sem round-trip para servidor
  • Offline: Funciona sem internet
  • Custo: Sem custo de GPU cloud

Se você está construindo aplicações que podem se beneficiar de ML, WebGPU é uma tecnologia que vale explorar agora.

Para entender mais sobre performance no JavaScript moderno, leia: VoidZero 2026: Toolchain Rust para JavaScript.

Bora pra cima! 🦅

Comentários (0)

Esse artigo ainda não possui comentários 😢. Seja o primeiro! 🚀🦅

Adicionar comentário