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 o mundo. A Web não é exceção.

Este ano, o Chrome adicionou recursos de IA generativa, incluindo a criação de temas personalizados e a ajuda para escrever o primeiro rascunho de texto. Mas a IA é muito mais do que isso. A IA pode enriquecer os próprios aplicativos da Web.

As páginas da Web podem incorporar componentes inteligentes para visão, como reconhecimento de rostos ou gestos, 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. Confira o curso IA prática no dispositivo para desenvolvedores Web.

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

Isso é importante por vários motivos:

  • Redução de custos: executar inferências no cliente do navegador reduz significativamente os custos de servidor, e isso pode ser especialmente útil para consultas de IA generativa, que podem ser muito mais caras do que consultas comuns.
  • Latência: para aplicativos que são particularmente sensíveis à latência, como aplicativos de áudio ou vídeo, todo o processamento ocorre no dispositivo, o que reduz a latência.
  • Privacidade: a execução no lado do cliente também pode desbloquear uma nova classe de aplicativos que exigem maior privacidade, em que os dados não podem ser enviados ao servidor.

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

Atualmente, os desenvolvedores e pesquisadores de aplicativos criam modelos usando frameworks, os modelos 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 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) empurram os tensores por meio de um gráfico de nós computacionais. Tensores são as entradas e saídas desses nós que realizam uma grande quantidade de computação nos dados.

Isso é importante, porque:

  • Tensores são estruturas de dados muito grandes que realizam computação em modelos que podem ter bilhões de pesos
  • O escalonamento e a inferência podem levar ao paralelismo de dados. Isso significa que as mesmas operações são realizadas em todos os elementos dos tensores.
  • O ML não requer precisão. Você pode precisar de um número de ponto flutuante de 64 bits para pousar na Lua, mas talvez precise 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 sejam executados de maneira mais rápida e fria, e até mesmo possibilitar a execução deles.

Enquanto isso, aqui nas equipes da WebAssembly e da WebGPU, estamos trabalhando para expor esses novos recursos aos desenvolvedores Web. Se você desenvolve aplicativos da Web, provavelmente não vai usar esses primitivos de baixo nível com frequência. Esperamos que os conjuntos de ferramentas ou frameworks que você está usando sejam compatíveis com novos recursos e extensões. Assim, você poderá se beneficiar com alterações mínimas na sua infraestrutura. Mas se você gosta de 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 para ser executado quase em velocidades nativas. O código é validado e executado em um ambiente em sandbox e com proteção de memória.

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

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

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

Seu aplicativo pode ser compilado uma vez e executado em qualquer lugar: um desktop, laptop, telefone ou qualquer outro dispositivo com um navegador. Confira Escreva uma vez e execute em qualquer lugar desenvolvido 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 para fins especiais. Em aplicativos nativos, é possível acessar computação para uso geral e para fins especiais, já que o aplicativo tem acesso aos recursos do dispositivo.

Na Web, quanto à portabilidade e segurança, avaliamos cuidadosamente qual conjunto de primitivos estão expostos. Isso equilibra a acessibilidade da Web com o desempenho máximo fornecido pelo hardware.

O WebAssembly é uma abstração portátil de CPUs, portanto, toda a inferência do Wasm é executada na CPU. Embora essa não seja a escolha de melhor desempenho, as CPUs têm ampla disponibilidade 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 dos aplicativos

Escolha 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, as inferências de CPU e GPU são comparáveis (executadas 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 holística dos aplicativos enquanto ouvimos os autores de frameworks e parceiros de aplicativos para desenvolver e lançar as melhorias mais solicitadas. Eles se enquadram em três categorias:

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

Computação mais rápida

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

Lembre-se, os modelos de ML nem sempre exigem altos níveis de precisão. SIMD relaxado é uma proposta que reduz alguns dos requisitos rigorosos que não são determinismo, levando a um codegen mais rápido para algumas operações vetoriais que são pontos de acesso para desempenho. Além disso, a descontraída SIMD apresenta um novo produto escalar e instruções da FMA que aceleram de 1,5 a 3 vezes as cargas de trabalho atuais. Isso 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 para valores de precisão única. Em comparação com valores de precisão única, há várias vantagens no uso de valores de meia precisão, requisitos de memória reduzidos, que permitem treinamento e implantação de redes neurais maiores e 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 os 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 (que).

O carregamento desses modelos no WebAssembly pode ser restritivo especialmente com modelos grandes, como temos atualmente. A proposta Memory64 remove essas restrições pela memória linear para que seja maior que 4 GB e correspondente ao espaço de endereço das plataformas nativas.

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

Melhor interoperabilidade na Web

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

Use o WebAssembly para levar 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 é essencial 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 do JavaScript (JSPI)

Os aplicativos típicos em C e C++ (bem como em muitas outras linguagens) geralmente são escritos em uma API síncrona. Isso significa que o aplicativo vai interromper a execução até que a operação seja concluída. Normalmente, esses aplicativos de bloqueio são mais intuitivos para escrever do que aplicativos com reconhecimento assíncrono.

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: um tamanho de código maior e não tão eficiente.

O exemplo a seguir é o cálculo de fibonacci, usando 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 JSPI a fim de acessar o resultado da promessa, da mesma forma que o faria para uma função normal.
  • A opção de linha de comando especial, -s ASYNCIFY=2. Isso invoca a opção de gerar um código que usa o VASTI para interagir com importações JavaScript que retornam promessas.

Para mais informações sobre a biblioteca JI, como usá-la e os benefícios dela, leia Introdução à API WebAssembly JavaScript promise Integration em v8.dev (link em inglês). Saiba mais sobre o teste de origem atual.

Controle de memória

Os desenvolvedores têm muito pouco controle sobre a memória Wasm. o módulo tem a própria memória. Qualquer API que precise acessar essa memória deve ser copiada ou copiada, e esse uso pode aumentar muito. Por exemplo, um aplicativo de gráficos pode precisar fazer cópias para cada frame.

A proposta de Controle de memória tem como objetivo fornecer um controle mais refinado sobre a memória linear do Wasm e reduzir o número de cópias em todo o pipeline do aplicativo. Esta proposta está na Fase 1, 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 muito maior, especialmente para modelos maiores e em dispositivos de última geração. Isso é válido tanto para aplicativos nativos quanto para aplicativos da Web.

O back-end escolhido depende do aplicativo, da estrutura ou do conjunto de ferramentas, além de outros fatores que influenciam o desempenho. Dito isso, continuamos a investir em propostas que permitam que o Wasm básico funcione bem com o restante da plataforma da Web e, mais especificamente, com a WebGPU.

Continuar lendo a parte 2