Mejoras de WebAssembly y WebGPU para lograr una IA web más rápida (parte 1)

Descubre cómo las mejoras de WebAssembly y WebGPU mejoran el rendimiento del aprendizaje automático en la Web.

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

Inferencia de IA en la Web

Todos conocemos la historia: la IA está transformando nuestro mundo. La Web no es la excepción.

Este año, Chrome agregó funciones basadas en IA generativa, incluida la creación de temas personalizados o ayudarte a escribir un primer borrador de texto. Pero la IA es mucho más que eso; La IA puede enriquecer las aplicaciones web por sí misma.

Las páginas web pueden incorporar componentes inteligentes para la visión, como detectar rostros o reconocer gestos, para la clasificación de audio o la detección de idiomas. El año pasado, vimos el éxito de la IA generativa, con algunas demostraciones impresionantes de modelos grandes de lenguaje en la Web. Asegúrate de consultar IA práctica integrada en el dispositivo para desarrolladores web.

Actualmente, la inferencia de IA en la Web está disponible en una gran sección de dispositivos, y el procesamiento de IA puede realizarse en la propia página web aprovechando el hardware del dispositivo del usuario.

Esto es muy importante por varios motivos:

  • Reducción de costos: Ejecutar inferencias en el cliente del navegador reduce de manera significativa los costos del servidor, y esto puede ser especialmente útil para las consultas de IA generativa, que pueden ser mucho más costosas que las consultas normales.
  • Latencia: En aplicaciones que son particularmente sensibles a la latencia, como aplicaciones de audio o video, hacer que todo el procesamiento ocurra en el dispositivo reduce la latencia.
  • Privacidad: Ejecutar en el lado del cliente también tiene el potencial de desbloquear una nueva clase de aplicaciones que requieren mayor privacidad, en la que los datos no se pueden enviar al servidor.

Cómo se ejecutan las cargas de trabajo de IA en la Web hoy en día

Hoy en día, los investigadores y desarrolladores de aplicaciones crean modelos con frameworks; estos se ejecutan en el navegador con un entorno de ejecución como Tensorflow.js o el entorno de ejecución de ONNX, y los entornos de ejecución usan APIs web para la ejecución.

Todos esos entornos de ejecución finalmente llegan a ejecutarse en la CPU a través de JavaScript o WebAssembly, o en la GPU a través de WebGL o WebGPU.

Diagrama de cómo se ejecutan las cargas de trabajo de IA en la Web hoy en día

Cargas de trabajo de aprendizaje automático

Las cargas de trabajo de aprendizaje automático (AA) envían los tensores a través de un grafo de nodos de procesamiento. Los tensores son las entradas y salidas de estos nodos que realizan una gran cantidad de cálculos sobre los datos.

Esto es importante por los siguientes motivos:

  • Los tensores son estructuras de datos muy grandes que realizan cálculos en modelos que pueden tener miles de millones de pesos
  • El escalamiento y la inferencia pueden conducir al paralelismo de datos. Esto significa que se realizan las mismas operaciones en todos los elementos de los tensores.
  • El AA no requiere precisión. Es posible que necesites un número de punto flotante de 64 bits para aterrizar en la Luna, pero tal vez solo necesites un mar de números de 8 bits o menos para el reconocimiento facial.

Por suerte, los diseñadores de chips agregaron funciones para hacer que los modelos se ejecuten más rápido, en menos tiempo y hasta para que sea posible ejecutarlos.

Mientras tanto, aquí en los equipos de WebAssembly y WebGPU, estamos trabajando para exponer esas nuevas capacidades a los desarrolladores web. Si eres un desarrollador de aplicaciones web, es poco probable que uses estas primitivas de bajo nivel con frecuencia. Esperamos que las cadenas de herramientas o los frameworks que uses sean compatibles con funciones y extensiones nuevas, por lo que puedes beneficiarte con cambios mínimos en tu infraestructura. Pero si lo que quieres es ajustar manualmente tus aplicaciones para mejorar el rendimiento, entonces estas funciones son relevantes para tu trabajo.

WebAssembly

WebAssembly (Wasm) es un formato de código de bytes compacto y eficiente que los entornos de ejecución pueden comprender y ejecutar. Está diseñado para aprovechar las capacidades del hardware subyacente, por lo que puede ejecutarse a velocidades casi nativas. El código se valida y se ejecuta en un entorno de zona de pruebas seguro para la memoria.

La información del módulo de Wasm se representa con una codificación binaria densa. En comparación con un formato basado en texto, eso implica una decodificación más rápida, una carga más rápida y un uso reducido de la memoria. Es portátil en el sentido de que no hace suposiciones sobre la arquitectura subyacente que aún no son comunes en las arquitecturas modernas.

La especificación de WebAssembly es iterativa y se trabaja en ella en un grupo de comunidad W3C abierto.

El formato binario no hace suposiciones sobre el entorno de host, por lo que también está diseñado para funcionar bien en incorporaciones que no sean web.

Tu aplicación se puede compilar una vez y ejecutarse en todas partes: una computadora de escritorio, una laptop, un teléfono o cualquier otro dispositivo con un navegador. Para obtener más información sobre este tema, consulta Escribe una vez y ejecuta en cualquier lugar con WebAssembly.

Ilustración de una laptop, una tablet y un teléfono

La mayoría de las aplicaciones de producción que ejecutan inferencias de IA en la Web usan WebAssembly, tanto para el procesamiento de la CPU como para las interfaces con procesamiento de propósito especial. En aplicaciones nativas, puedes acceder a procesamiento de uso general y de uso especial, ya que la aplicación puede acceder a las capacidades del dispositivo.

En la Web, para garantizar la portabilidad y la seguridad, evaluamos cuidadosamente qué conjunto de primitivas se exponen. Esto equilibra la accesibilidad de la Web con el máximo rendimiento proporcionado por el hardware.

WebAssembly es una abstracción portátil de las CPU, por lo que toda la inferencia de Wasm se ejecuta en la CPU. Si bien esta no es la opción con mejor rendimiento, las CPU están ampliamente disponibles y funcionan en la mayoría de las cargas de trabajo y en la mayoría de los dispositivos.

Para cargas de trabajo más pequeñas, como cargas de trabajo de texto o audio, la GPU sería costosa. Hay una serie de ejemplos recientes en los que Wasm es la opción correcta:

Puedes descubrir mucho más en las demostraciones de código abierto, como whisper-tiny, llama.cpp y Gemma2B ejecutándose en el navegador.

Adopta un enfoque integral en tus aplicaciones

Debes elegir primitivas en función del modelo de AA particular, la infraestructura de aplicaciones y la experiencia general de la aplicación prevista para los usuarios

Por ejemplo, en la detección de puntos de referencia faciales de MediaPipe, la inferencia de CPU y GPU son comparables (se ejecutan en un dispositivo Apple M1), pero hay modelos en los que la varianza podría ser significativamente mayor.

Cuando se trata de cargas de trabajo de AA, consideramos una vista integral de la aplicación y escuchamos a los autores del framework y a los socios de las aplicaciones para desarrollar y enviar las mejoras más solicitadas. A grandes rasgos, se dividen en tres categorías:

  • Expón las extensiones de CPU que son fundamentales para el rendimiento
  • Habilitar la ejecución de modelos más grandes
  • Habilita la interoperabilidad sin inconvenientes con otras APIs web

Procesamiento más rápido

Actualmente, la especificación de WebAssembly solo incluye un determinado conjunto de instrucciones que exponemos a la Web. Sin embargo, el hardware sigue agregando instrucciones nuevas que aumentan la brecha entre el rendimiento nativo y de WebAssembly.

Recuerda, los modelos de AA no siempre requieren altos niveles de precisión. SIMD relajada es una propuesta que reduce algunos de los requisitos estrictos de no determinismo, lo que lleva a una generación de códecs más rápida para algunas operaciones vectoriales que son puntos problemáticos para el rendimiento. Además, relajado SIMD presenta instrucciones nuevas de producto punto y FMA que aceleran las cargas de trabajo existentes de 1.5 a 3 veces. Se envió en Chrome 114.

El formato de punto flotante con precisión media usa 16 bits para IEEE FP16 en lugar de los de 32 bits que se usan para valores de precisión simples. En comparación con los valores de precisión simple, existen varias ventajas en usar valores de precisión media, la reducción de los requisitos de memoria, lo que permite el entrenamiento y la implementación de redes neuronales más grandes y un ancho de banda de memoria reducido. La precisión reducida acelera la transferencia de datos y las operaciones matemáticas.

Modelos más grandes

Los punteros a la memoria lineal de Wasm se representan como números enteros de 32 bits. Esto tiene dos consecuencias: los tamaños de montón se limitan a 4 GB (cuando las computadoras tienen mucha más RAM física que esa) y el código de la app que se orienta a Wasm debe ser compatible con un tamaño de puntero de 32 bits (que).

Especialmente con modelos grandes como los que tenemos hoy, cargar estos modelos en WebAssembly puede ser restrictivo. La propuesta Memory64 quita estas restricciones por memoria lineal para que sea superior a 4 GB y coincida con el espacio de direcciones de las plataformas nativas.

Tenemos una implementación completamente funcional en Chrome y se estima que la lanzaremos más adelante este año. Por ahora, puedes ejecutar experimentos con la marca chrome://flags/#enable-experimental-webassembly-features y enviarnos comentarios.

Mejor interoperabilidad web

WebAssembly podría ser el punto de entrada para el procesamiento con propósitos especiales en la Web.

WebAssembly se puede usar para llevar aplicaciones de GPU a la Web. Esto significa que la misma aplicación de C++ que puede ejecutarse en el dispositivo también puede ejecutarse en la Web, con pequeñas modificaciones.

Emscripten, la cadena de herramientas del compilador de Wasm, ya tiene vinculaciones para WebGPU. Es el punto de entrada a la inferencia de IA en la Web, por lo que es fundamental que Wasm pueda interoperar sin problemas con el resto de la plataforma web. Estamos trabajando en varias propuestas diferentes en este espacio.

Integración de promesa de JavaScript (JSPI)

Las aplicaciones típicas de C y C++ (así como muchos otros lenguajes) se escriben comúnmente en una API síncrona. Esto significa que la aplicación detendrá la ejecución hasta que la operación se complete. Estas aplicaciones de bloqueo suelen tener una escritura más intuitiva que las aplicaciones asíncronas.

Cuando las operaciones costosas bloquean el subproceso principal, pueden bloquear la E/S, y los usuarios pueden ver el bloqueo. Hay una discrepancia entre un modelo de programación síncrono de aplicaciones nativas y el modelo asíncrono de la Web. Esto es particularmente problemático para aplicaciones heredadas, que serían costosas de migrar. Emscripten proporciona una forma de hacerlo con Asyncify, pero esta no siempre es la mejor opción: un código más grande y menos eficiente.

En el siguiente ejemplo, se calcula fibonacci, mediante promesas de JavaScript para la adición.

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

En este ejemplo, presta atención a lo siguiente:

  • La macro EM_ASYNC_JS genera todo el código de adhesión necesario para que podamos usar JSPI con el objetivo de acceder al resultado de la promesa, como lo haría con una función normal.
  • La opción de línea de comandos especial, -s ASYNCIFY=2. Esto invoca la opción de generar código que usa JSPI para interactuar con las importaciones de JavaScript que muestran promesas.

Para obtener más información sobre JSPI, cómo usarlo y sus beneficios, lee Presentación de la API de WebAssembly JavaScript Promise Integration en la versión v8.dev. Obtén más información sobre la prueba de origen actual.

Control de la memoria

Los desarrolladores tienen muy poco control sobre la memoria de Wasm. el módulo posee su propia memoria. Cualquier API que necesite acceder a esta memoria debe copiarse o copiarse, y este uso puede aumentar. Por ejemplo, una aplicación gráfica puede necesitar copiar dentro y fuera de cada fotograma.

La propuesta de control de memoria tiene como objetivo proporcionar un control más detallado sobre la memoria lineal de Wasm y reducir la cantidad de copias en la canalización de la aplicación. Esta propuesta se encuentra en la Fase 1. Estamos prototiparla en V8, el motor JavaScript de Chrome, para informar la evolución del estándar.

Decide qué backend es el adecuado para ti

Si bien la CPU es omnipresente, no siempre es la mejor opción. El procesamiento de propósito especial en la GPU o en los aceleradores puede ofrecer un rendimiento de órdenes de magnitud mayor, en especial en modelos más grandes y en dispositivos de alta gama. Esto se aplica tanto a las aplicaciones nativas como a las aplicaciones web.

El backend que elijas depende de la aplicación, el framework o la cadena de herramientas, así como de otros factores que influyen en el rendimiento. Dicho esto, seguiremos invirtiendo en propuestas que permitan que el núcleo de Wasm funcione bien con el resto de la plataforma web y, más específicamente, con WebGPU.

Continúa leyendo la Parte 2