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

Saiba como os aprimoramentos do WebAssembly e do WebGPU melhoram o desempenho do aprendizado de máquina na Web.

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

Inferência de IA na Web

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

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

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

Hoje, 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:

  • Custos reduzidos: executar a inferência no cliente do navegador reduz significativamente os custos do servidor. Isso pode ser especialmente útil para consultas de IA geral, que podem ser muito mais caras do que as consultas normais.
  • Latência: para aplicativos que são particularmente sensíveis à latência, como aplicativos de áudio ou vídeo, fazer todo o processamento no dispositivo 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 mais privacidade, em que os dados não podem ser enviados ao servidor.

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

Atualmente, desenvolvedores de aplicativos e pesquisadores criam modelos usando frameworks, que são executados no navegador com um ambiente de execução, como o Tensorflow.js ou o ONNX Runtime Web.

Todos esses ambientes de execução são executados na CPU usando JavaScript ou WebAssembly ou na GPU usando WebGL ou WebGPU.

Diagrama de como os workloads de IA são executados na Web atualmente

Cargas de trabalho de machine learning

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

Isso é importante porque:

  • 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 ao paralelismo de dados. Isso significa que as mesmas operações são realizadas em todos os elementos dos tensores.
  • O ML não exige precisão. Talvez você precise 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 reconhecimento facial.

Felizmente, os designers de chips adicionaram recursos para que os modelos funcionem mais rápido, sejam mais legais e até mesmo sejam executados.

Enquanto isso, as equipes do WebAssembly e do WebGPU estão trabalhando para expor esses novos recursos aos desenvolvedores da Web. Se você é um desenvolvedor de aplicativos da Web, é improvável que use essas primitivas de baixo nível com frequência. Esperamos que as cadeias 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 sua infraestrutura. No entanto, se você gosta de ajustar manualmente os aplicativos para melhorar o desempenho, esses recursos são relevantes para 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 que possa ser executado em velocidades quase nativas. O código é validado e executado em um ambiente sandbox seguro para a 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 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 não são comuns em arquiteturas modernas.

A especificação do 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, ele foi projetado para funcionar bem em incorporações que não são da Web.

O aplicativo pode ser compilado uma vez e executado em qualquer lugar: em um computador, laptop, smartphone ou qualquer outro dispositivo com um navegador. Confira Write once, run anywhere finalmente realizado com o WebAssembly para saber mais sobre isso.

Ilustração de um laptop, tablet e 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 de finalidade especial, já que o aplicativo pode acessar os recursos do dispositivo.

Na Web, para portabilidade e segurança, avaliamos cuidadosamente quais conjuntos de primitivos sã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 opção com 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.

Use uma abordagem holística para seus aplicativos

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

Por exemplo, na detecção de pontos de referência faciais do MediaPipe, a inferência da CPU e da 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.

No caso de cargas de trabalho de ML, consideramos uma visão holística do aplicativo, enquanto ouvimos autores de frameworks e parceiros de aplicativos para desenvolver e enviar as melhorias mais solicitadas. Elas se dividem em três categorias:

  • Exposição de extensões de CPU essenciais para o desempenho
  • Ativar a execução de modelos maiores
  • Ativar a interoperabilidade perfeita com outras APIs da Web

Computação mais rápida

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

Os modelos de ML nem sempre exigem altos níveis de precisão. O SIMD relaxado é uma proposta que reduz alguns dos requisitos estritos de não determinismo, levando a um tempo de geração de código mais rápido para algumas operações de vetor que são pontos de acesso para desempenho. Além disso, o SIMD relaxado apresenta um novo produto escalar e instruções FMA que aceleram as cargas de trabalho atuais de 1,5 a 3 vezes. 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 usados para valores de precisão única. Em comparação com valores de precisão simples, 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 e a largura de banda de memória reduzida. A redução da precisão 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 pilha 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 que tem como destino o Wasm precisa ser compatível com um tamanho de ponteiro de 32 bits (que).

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

Temos uma implementação completa no Chrome, e ela deve ser lançada 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 computação de finalidade especial na Web.

O WebAssembly pode ser usado 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. Portanto, é fundamental que o Wasm possa interagir perfeitamente com o restante da plataforma da Web. Estamos trabalhando em algumas propostas diferentes para esse espaço.

Integração de promessas do JavaScript (JSPI, na sigla em inglês)

Aplicativos típicos em C e C++ (assim como em muitas outras linguagens) geralmente são escritos com base em uma API síncrona. Isso significa que o aplicativo vai interromper a execução até que a operação seja concluída. Esses aplicativos de bloqueio geralmente são mais intuitivos de programar do que os aplicativos que são compatíveis com a execução assíncrona.

Quando operações caras bloqueiam a linha de execução principal, elas podem bloquear a E/S, e o travamento 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 para portar. O Emscripten oferece uma maneira de fazer isso com o Asyncify, mas essa não é sempre a melhor opção, já que o tamanho do código é maior e não é tão eficiente.

O exemplo a seguir calcula a sequência de Fibonacci usando promessas 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 no seguinte:

  • A macro EM_ASYNC_JS gera todo o código de união necessário para que possamos usar a JSPI e acessar o resultado da promessa, assim como faríamos com uma função normal.
  • A opção especial de linha de comando, -s ASYNCIFY=2. Isso invoca a opção de gerar um código que usa a JSPI para interagir com importações JavaScript que retornam promessas.

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

Controle de memória

Os desenvolvedores têm pouco controle sobre a memória do Wasm. O módulo tem a própria memória. Todas as APIs que precisam acessar essa memória precisam copiar para dentro ou para fora, e esse uso pode aumentar. Por exemplo, um aplicativo gráfico pode precisar copiar e copiar 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 no pipeline do aplicativo. Essa proposta está na fase 1, e estamos criando um protótipo dela no V8, o mecanismo JavaScript do Chrome, para informar a evolução do padrão.

Decidir qual back-end é ideal para você

Embora a CPU seja onipresente, ela nem sempre é a melhor opção. A computação de finalidade especial na GPU ou nos aceleradores pode oferecer desempenho muito maior, especialmente para modelos maiores e dispositivos de ponta. Isso vale para aplicativos nativos e da Web.

A escolha do back-end depende do aplicativo, do framework ou da cadeia de ferramentas, além de outros fatores que influenciam a performance. No entanto, continuamos investindo em propostas que permitem que o Wasm funcione bem com o restante da plataforma da Web e, mais especificamente, com o WebGPU.

Continue lendo a Parte 2