Melhorias na WebAssembly e na WebGPU para oferecer uma IA da Web mais rápida, parte 1

Saiba como as melhorias do WebAssembly e da WebGPU melhoram o desempenho do machine learning na Web.

Austin Eng
Austin Eng
Deepti Gandluri
Deepti Gandluri
François Beaufort
François Beaufort

Inferência de IA na Web

Todos nós já ouvimos a história: a IA está transformando nosso mundo. A Web não é exceção.

Este ano, o Chrome adicionou recursos de IA generativa, como a criação de temas personalizados ou a ajuda para escrever o primeiro rascunho de um texto. Mas a IA é muito mais do que isso. Ela pode enriquecer aplicativos da Web por conta própria.

As páginas da Web podem incorporar componentes inteligentes para visão, como a identificação de rostos ou o reconhecimento de gestos, para classificação de áudio ou detecção de idioma. No ano passado, vimos a IA generativa decolar, incluindo algumas demonstrações realmente impressionantes de modelos de linguagem grandes na Web. Consulte IA prática no dispositivo para desenvolvedores da Web.

A inferência de IA na Web já está disponível em uma grande seção de dispositivos, e o processamento de IA pode acontecer na própria página da Web, usando o hardware do dispositivo do usuário.

Isso é eficiente por vários motivos:

  • Custos reduzidos: a execução de inferências no cliente do navegador reduz significativamente os custos do servidor. Isso pode ser especialmente útil para consultas da IA generativa, que podem ser muito mais caras do que as consultas regulares.
  • Latência: para aplicativos particularmente sensíveis à latência, como aplicativos de áudio ou vídeo, todo o processamento acontece no dispositivo. Isso reduz a latência.
  • Privacidade: a execução no lado do cliente também tem o potencial de desbloquear uma nova classe de aplicativos que exigem maior privacidade, onde os dados não podem ser enviados para o servidor.

Como as cargas de trabalho de IA são executadas na Web atualmente

Atualmente, os desenvolvedores e pesquisadores de aplicativos criam modelos usando frameworks, eles são executados no navegador usando um ambiente de execução como o Tensorflow.js ou o ONNX Runtime Web e os ambientes de execução usam as APIs da Web para execução.

Todos esses tempos de execução acabam se tornando executados na CPU por meio de JavaScript ou WebAssembly ou na GPU por meio do WebGL ou da WebGPU.

Diagrama de como as cargas de trabalho de IA são executadas na Web atualmente

Cargas de trabalho de machine learning

As cargas de trabalho de machine learning (ML) enviam tensores por um gráfico de nós computacionais. Tensores são as entradas e saídas desses nós que executam uma grande quantidade de cálculo nos dados.

Isso é importante pelos seguintes motivos:

  • Tensores são estruturas de dados muito grandes, que realizam cálculos em modelos que podem ter bilhões de pesos
  • O escalonamento e a inferência podem levar a um paralelismo de dados. Isso significa que as mesmas operações são executadas em todos os elementos dos tensores.
  • O ML não exige precisão. Você pode precisar de um número de ponto flutuante de 64 bits para pousar na lua, mas pode precisar apenas de um mar de números de 8 bits ou menos para o reconhecimento facial.

Felizmente, os designers de chips adicionaram recursos para fazer com que os modelos funcionem mais rápido, mais frio e até mesmo permitam executá-los.

Enquanto isso, aqui nas equipes da WebAssembly e da WebGPU, estamos trabalhando para expor esses novos recursos aos desenvolvedores da Web. Se você é um desenvolvedor de aplicativos da Web, provavelmente não usará esses primitivos de baixo nível com frequência. Esperamos que os conjuntos de ferramentas ou frameworks que você está usando ofereçam suporte a novos recursos e extensões, para que você possa se beneficiar com mudanças mínimas na infraestrutura. Mas se você quiser ajustar manualmente o desempenho dos seus aplicativos, esses recursos são relevantes para o seu trabalho.

WebAssembly

O WebAssembly (Wasm) é um formato de código de bytes compacto e eficiente que os ambientes de execução podem entender e executar. Ele foi projetado para aproveitar os recursos de hardware subjacentes e pode ser executado em velocidades quase nativas. O código é validado e executado em um ambiente no modo sandbox e com segurança de memória.

As informações do módulo Wasm são representadas por uma codificação binária densa. Em comparação com um formato baseado em texto, isso significa decodificação mais rápida, carregamento mais rápido e uso reduzido de memória. Ele é portátil no sentido de não fazer suposições sobre a arquitetura subjacente que ainda não são comuns às arquiteturas modernas.

A especificação WebAssembly é iterativa e trabalha em um grupo da comunidade W3C aberto.

O formato binário não faz suposições sobre o ambiente do host, por isso foi projetado para funcionar bem em embeddings que não sejam da Web também.

Seu aplicativo pode ser compilado uma vez e executado em qualquer lugar: em um computador, laptop, telefone ou qualquer outro dispositivo com navegador. Confira Escreva uma vez, execute em qualquer lugar finalmente percebido com o WebAssembly para saber mais sobre isso.

Ilustração de um laptop, um tablet e um smartphone

A maioria dos aplicativos de produção que executam inferência de IA na Web usa o WebAssembly, tanto para computação de CPU quanto para interface com computação de finalidade especial. Em aplicativos nativos, é possível acessar computação de uso geral e especial, já que o aplicativo pode acessar os recursos do dispositivo.

Na Web, para fins de portabilidade e segurança, avaliamos cuidadosamente qual conjunto de primitivos é exposto. Isso equilibra a acessibilidade da Web com o desempenho máximo oferecido pelo hardware.

O WebAssembly é uma abstração portátil das CPUs. Portanto, toda a inferência do Wasm é executada na CPU. Embora essa não seja a escolha de melhor desempenho, as CPUs estão amplamente disponíveis e funcionam na maioria das cargas de trabalho, na maioria dos dispositivos.

Para cargas de trabalho menores, como cargas de trabalho de texto ou áudio, a GPU seria cara. Há vários exemplos recentes em que o Wasm é a escolha certa:

Você pode descobrir ainda mais em demonstrações de código aberto, como: whisper-tiny, llama.cpp e Gemma2B em execução no navegador.

Adote uma abordagem holística para seus aplicativos

Escolha os primitivos com base no modelo de ML específico, na infraestrutura do aplicativo e na experiência geral do aplicativo pretendida para os usuários.

Por exemplo, na detecção de pontos de referência facial do MediaPipe, a inferência de CPU e de GPU são comparáveis (em execução em um dispositivo Apple M1), mas há modelos em que a variação pode ser significativamente maior.

Quando se trata de cargas de trabalho de ML, consideramos uma visão completa dos aplicativos, enquanto ouvimos autores de framework e parceiros de aplicativos para desenvolver e enviar as melhorias mais solicitadas. Eles se dividem em três categorias:

  • Expor extensões de CPU essenciais para o desempenho
  • Permitir a execução de modelos maiores
  • Permitir interoperabilidade ininterrupta com outras APIs da Web

Computação mais rápida

Hoje, as especificações do WebAssembly incluem apenas um determinado conjunto de instruções que expomos à Web. No entanto, o hardware continua adicionando instruções mais recentes que aumentam a lacuna entre o desempenho do nativo e do WebAssembly.

Lembre-se de que os modelos de ML nem sempre exigem altos níveis de precisão. O chip descontraído é uma proposta que reduz alguns dos requisitos rígidos que não são determinismo, levando a uma codegen mais rápida para algumas operações vetoriais que são pontos importantes para o desempenho. Além disso, o SIMD descontraído apresenta um novo produto escalar e instruções de FMA que aceleram as cargas de trabalho existentes de 1,5 a 3 vezes. Ele foi enviado no Chrome 114.

O formato de ponto flutuante de meia precisão usa 16 bits para IEEE FP16 em vez dos 32 bits usados para valores de precisão única. Em comparação com os valores de precisão única, há várias vantagens no uso de valores de meia precisão, requisitos de memória reduzidos, que permitem o treinamento e a implantação de redes neurais maiores, largura de banda de memória reduzida. A precisão reduzida acelera a transferência de dados e as operações matemáticas.

Modelos maiores

Os ponteiros na memória linear do Wasm são representados como números inteiros de 32 bits. Isso tem duas consequências: os tamanhos de heap são limitados a 4 GB (quando computadores têm muito mais RAM física do que isso), e o código do aplicativo destinado ao Wasm precisa ser compatível com um tamanho de ponteiro de 32 bits (o que).

Especialmente com modelos grandes como temos hoje, o carregamento desses modelos no WebAssembly pode ser restritivo. A proposta Memory64 remove essas restrições por memória linear para que sejam maiores que 4 GB e correspondam ao espaço de endereço de plataformas nativas.

Temos uma implementação completa em funcionamento no Chrome e esperamos que seja enviada ainda este ano. Por enquanto, você pode fazer experimentos com a flag chrome://flags/#enable-experimental-webassembly-features e enviar feedback.

Melhor interoperabilidade da Web

O WebAssembly pode ser o ponto de entrada para computação para fins especiais na Web.

O WebAssembly pode ser usado para trazer aplicativos de GPU para a Web. Isso significa que o mesmo aplicativo C++ que pode ser executado no dispositivo também pode ser executado na Web com pequenas modificações.

O Emscripten, o conjunto de ferramentas do compilador Wasm, já tem vinculações para a WebGPU. Ele é o ponto de entrada para a inferência de IA na Web, então é fundamental que o Wasm possa interoperar perfeitamente com o restante da plataforma da Web. Estamos trabalhando em algumas propostas diferentes neste espaço.

Integração de promessas de JavaScript (JSPI)

Aplicativos C e C++ típicos (assim como muitas outras linguagens) costumam ser escritos com uma API síncrona. Isso significa que o aplicativo pararia a execução até que a operação fosse concluída. Esses aplicativos de bloqueio costumam ser mais intuitivos de escrever do que aplicativos que reconhecem assincronia.

Quando operações caras bloqueiam a linha de execução principal, elas podem bloquear a E/S e a instabilidade fica visível para os usuários. Há uma incompatibilidade entre um modelo de programação síncrona de aplicativos nativos e o modelo assíncrono da Web. Isso é especialmente problemático para aplicativos legados, que seriam caros de transferir. O Emscripten oferece uma maneira de fazer isso com o Asyncify, mas essa nem sempre é a melhor opção: código maior e não tão eficiente.

O exemplo a seguir é o cálculo do fibonacci com promessas de JavaScript para adição.

long promiseFib(long x) {
 if (x == 0)
   return 0;
 if (x == 1)
   return 1;
 return promiseAdd(promiseFib(x - 1), promiseFib(x - 2));
}
// promise an addition
EM_ASYNC_JS(long, promiseAdd, (long x, long y), {
  return Promise.resolve(x+y);
});
emcc -O3 fib.c -o b.html -s ASYNCIFY=2

Neste exemplo, preste atenção ao seguinte:

  • A macro EM_ASYNC_JS gera todo o código agrupador necessário para que possamos usar o RedisI a fim de acessar o resultado da promessa, da mesma forma que faria para uma função normal.
  • A opção especial de linha de comando, -s ASYNCIFY=2. Isso invoca a opção para gerar o código que usa a JavaScriptI para interagir com importações de JavaScript que retornam promessas.

Para saber mais sobre a OpenAPII, como usá-la e os benefícios dela, leia Introdução à API WebAssembly JavaScript Promise Integration em v8.dev. Saiba mais sobre o teste de origem atual.

Controle de memória

Os desenvolvedores têm pouco controle sobre a memória Wasm, porque o módulo tem a própria memória. Todas as APIs que precisam acessar essa memória precisam ser copiadas para dentro ou fora, e esse uso pode aumentar muito. Por exemplo, um aplicativo gráfico pode precisar copiar e copiar para cada frame.

A proposta de Controle de memória visa fornecer controle mais refinado sobre a memória linear Wasm e reduzir o número de cópias em todo o pipeline do aplicativo. Esta proposta está na Fase 1, e estamos fazendo a prototipagem dela no V8, o mecanismo JavaScript do Chrome, para informar a evolução do padrão.

Decida qual back-end é ideal para você

Embora a CPU seja onipresente, ela nem sempre é a melhor opção. A computação para fins especiais na GPU ou nos aceleradores pode oferecer um desempenho em ordem de magnitude, especialmente para modelos maiores e em dispositivos de última geração. Isso é válido para aplicativos nativos e da Web.

O back-end escolhido depende do aplicativo, da estrutura ou do conjunto de ferramentas, bem como de outros fatores que influenciam o desempenho. Por isso, continuamos investindo em propostas que permitam que o Wasm principal funcione bem com o restante da plataforma da Web e, mais especificamente, com a WebGPU.

Continuar lendo a parte 2