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 la IA en la Web

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

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

Las páginas web pueden incorporar componentes inteligentes para la visión, como identificar rostros o reconocer gestos, para clasificar audio o detectar idiomas. En el último año, vimos el despegue de la IA generativa, con algunas demostraciones realmente impresionantes de modelos grandes de lenguaje en la Web. Asegúrate de consultar Práctica de la IA en el dispositivo para desarrolladores web.

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

Esto es muy útil por varios motivos:

  • Costos reducidos: Ejecutar inferencias en el cliente del navegador reduce significativamente los costos del servidor, lo que puede ser muy útil para las consultas de IA generativa, que pueden ser mucho más costosas que las consultas normales.
  • Latencia: En el caso de las aplicaciones que son muy sensibles a la latencia, como las de audio o video, cuando todo el procesamiento se realiza en el dispositivo, se reduce la latencia.
  • Privacidad: La ejecución 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 hoy las cargas de trabajo de IA en la Web

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

Todos esos tiempos de ejecución terminan finalmente en ejecución 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 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 procesamiento 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 generar 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 llegar a la Luna, pero es posible que 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 que los modelos se ejecuten más rápido, se enfríen y hasta se puedan ejecutar.

Mientras tanto, 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 frameworks que usas sean compatibles con nuevas funciones y extensiones, por lo que puedes beneficiarte con cambios mínimos en tu infraestructura. Pero si te gusta ajustar manualmente tus aplicaciones para mejorar el rendimiento, 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. Se diseñó para aprovechar las capacidades subyacentes del hardware, de modo que pueda 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 Wasm se representa con una codificación binaria densa. En comparación con un formato basado en texto, eso significa 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 WebAssembly es iterativa y se trabaja en un grupo comunitario de W3C abierto.

El formato binario no hace suposiciones sobre el entorno del host, por lo que también está diseñado para funcionar bien en incorporaciones que no son 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 Write once, run dondequiera que esté disponible finalmente 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 utilizan WebAssembly, tanto para el procesamiento de CPU como para las interfaces con procesamiento de propósitos especiales. En las 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, por cuestiones de portabilidad y seguridad, evaluamos cuidadosamente qué conjunto de primitivas se expone. Esto equilibra la accesibilidad de la web con el rendimiento máximo proporcionado por el hardware.

WebAssembly es una abstracción portátil de 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 varios ejemplos recientes en los que Wasm es la opción correcta:

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

Adopta un enfoque integral para tus aplicaciones

Debes elegir primitivas según el modelo de AA específico, la infraestructura de la aplicación y la experiencia de aplicación general prevista para los usuarios.

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

Cuando se trata de cargas de trabajo de AA, consideramos una vista integral de la aplicación, mientras escuchamos a los autores de frameworks y los socios de aplicaciones, para desarrollar y entregar 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
  • Habilitar la interoperabilidad sin interrupciones con otras APIs web

Procesamiento más rápido

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

Recuerda que los modelos de AA no siempre requieren altos niveles de precisión. SIMD flexibilizado es una propuesta que reduce algunos de los requisitos estrictos y no determinismo, lo que genera un codegen más rápido para algunas operaciones vectoriales que son hotspots para el rendimiento. Además, Tranquilo SIMD presenta un nuevo producto escalar y instrucciones de FMA que aceleran las cargas de trabajo existentes de 1.5 a 3 veces. Esto se envió en Chrome 114.

El formato de punto flotante de precisión media usa 16 bits para el IEEE FP16 en lugar de los 32 bits que se usan para los valores de precisión simples. En comparación con los valores de precisión simple, existen varias ventajas en el uso de valores de precisión media, los requisitos de memoria reducidos, que permiten el entrenamiento y la implementación de redes neuronales más grandes, y la reducción del ancho de banda de memoria. 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 están limitados a 4 GB (cuando las computadoras tienen mucha más RAM física que esa) y el código de la aplicación que apunta a Wasm debe ser compatible con un tamaño de puntero de 32 bits (que).

Especialmente con modelos grandes como los que tenemos hoy en día, 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 enviaremos 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 procesamiento con un propósito especial en la Web.

WebAssembly se puede usar para llevar las aplicaciones de GPU a la Web. Eso significa que la misma aplicación de C++ que se puede ejecutar 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 para 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 un par de propuestas diferentes en este espacio.

Integración de promesas 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 esta finalice. Estas aplicaciones de bloqueo suelen ser más intuitivas de escribir que las aplicaciones que son compatibles con el modo asíncrono.

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 las aplicaciones heredadas, que su portabilidad sería costosa. Emscripten proporciona una manera de hacerlo con Asyncify, pero no siempre es la mejor opción: un código más grande y no tan eficiente.

El siguiente ejemplo es el cómputo de fibonacci, mediante el uso de 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 unión necesario a fin de que podamos usar JSPI para acceder al resultado de la promesa, como lo haría con una función normal.
  • La opción especial de línea de comandos, -s ASYNCIFY=2. Esto invoca la opción de generar código que use JSPI para interactuar con las importaciones de JavaScript que muestren promesas.

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

Control de memoria

Los desarrolladores tienen muy poco control sobre la memoria de Wasm; el módulo tiene su propia memoria. Cualquier API que necesite acceder a esta memoria debe copiarse o copiarse, y este uso realmente puede aumentar. Por ejemplo, es posible que una aplicación de gráficos deba copiar y copiar para 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. La propuesta se encuentra en la fase 1. Su prototipo se encuentra en V8, el motor JavaScript de Chrome, para informar la evolución del estándar.

Decide qué backend es adecuado para ti

Si bien la CPU está presente en todos lados, no siempre es la mejor opción. El procesamiento con propósito especial en la GPU o los aceleradores puede ofrecer un rendimiento en órdenes de magnitud mayor, en especial para modelos más grandes y en dispositivos de alta gama. Esto es así tanto para las aplicaciones nativas como para las aplicaciones web.

El backend que elijas depende de la aplicación, el framework o la cadena de herramientas, además de otros factores que influyen en el rendimiento. Dicho esto, seguimos 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.

Continuar leyendo la parte 2