Ejecución de Modelos ONNX

El camino práctico a WebNN es a través de ONNX Runtime Web:

import * as ort from 'onnxruntime-web';

async function runInference(imageData) {
 const session = await ort.InferenceSession.create(
 'mobilenet_v3_small_int8.onnx',
 {
 executionProviders: [
 {
 name: 'webnn',
 deviceType: 'npu',
 powerPreference: 'default',
 },
 ],
 fallbackExecutionProviders: ['wasm'],
 },
 );

 const tensor = new ort.Tensor('float32', preprocessImage(imageData), [1, 3, 224, 224]);

 const startTime = performance.now();
 const results = await session.run({ input: tensor });
 const elapsed = performance.now() - startTime;

 console.log(`Tiempo de inferencia: ${elapsed.toFixed(2)}ms`);

 const probabilities = softmax(results.output.data);
 return getTopK(probabilities, 5);
}

function preprocessImage(imageData) {
 const canvas = new OffscreenCanvas(224, 224);
 const ctx = canvas.getContext('2d');
 ctx.drawImage(imageData, 0, 0, 224, 224);

 const pixels = ctx.getImageData(0, 0, 224, 224).data;
 const float32 = new Float32Array(3 * 224 * 224);

 const mean = [0.485, 0.456, 0.406];
 const std = [0.229, 0.224, 0.225];

 for (let i = 0; i < 224 * 224; i++) {
 float32[i] = (pixels[i * 4] / 255 - mean[0]) / std[0];
 float32[i + 224 * 224] = (pixels[i * 4 + 1] / 255 - mean[1]) / std[1];
 float32[i + 2 * 224 * 224] = (pixels[i * 4 + 2] / 255 - mean[2]) / std[2];
 }

 return float32;
}

Benchmarks Reales

Se benchmarkearon tres modelos en diferentes backends en dos máquinas:

Máquina 1: Intel Core Ultra 7 (Meteor Lake), 16GB RAM, Chrome 126

Modelo WASM (CPU) WebGPU WebNN (NPU) WebNN (GPU)
MobileNetV3-Small (INT8) 18ms 4.2ms 2.8ms 3.1ms
MobileNetV3-Large (FP32) 45ms 8.5ms , 7.2ms
ResNet-50 (INT8) 125ms 15ms 11ms 12ms
DistilBERT (INT8, seq=128) 340ms 42ms 28ms 35ms

Máquina 2: Apple M3 Pro, 18GB RAM, Chrome 126

Modelo WASM (CPU) WebGPU WebNN (GPU)*
MobileNetV3-Small (INT8) 12ms 3.8ms 3.2ms
ResNet-50 (INT8) 95ms 12ms 10ms

*WebNN en macOS usa el delegado CoreML, que routea al ANE (Apple Neural Engine).

Patrones de IA que Preservan la Privacidad

El caso de uso más convincente para inferencia on-device no es el performance, es la privacidad:

async function analyzeSkinLesion(image) {
 const session = await getOrCreateSession('dermnet_classifier_int8.onnx');
 const tensor = preprocessDermatologyImage(image);

 // Inferencia local, la imagen nunca se manda a un server
 const results = await session.run({ input: tensor });

 // Solo el resultado de clasificación se manda al server
 await reportClassification(results.classification, {
 confidence: results.confidence,
 });

 return results;
}

Este patrón se ha aplicado exitosamente en clasificación de documentos para aplicaciones de legal tech. Los documentos se procesan con OCR localmente (usando Tesseract.js), después se clasifican con un modelo DistilBERT corriendo a través de WebNN. El contenido del documento nunca sale del dispositivo.

Optimización de Modelos para On-Device

# Paso 1: Exportar a ONNX desde PyTorch
torch.onnx.export(model, dummy_input, "model_fp32.onnx", opset_version=17)

# Paso 2: Cuantizar a INT8
from onnxruntime.quantization import quantize_dynamic, QuantType
quantize_dynamic("model_fp32.onnx", "model_int8.onnx", weight_type=QuantType.QInt8)

# Paso 3: Optimizar grafo para WebNN
import onnxoptimizer
model = onnx.load("model_int8.onnx")
optimized = onnxoptimizer.optimize(model, [
 'eliminate_deadend',
 'fuse_bn_into_conv',
 'fuse_consecutive_transposes',
])
onnx.save(optimized, "model_int8_optimized.onnx")

Resultado: MobileNetV3-Small pasa de 14.3MB (FP32) a 3.8MB (INT8 cuantizado). Lo suficientemente pequeño para cachear en un Service Worker.

Para Cerrar

WebNN cierra la brecha entre "ML en el browser es un juguete" y "ML en el browser es viable en producción." El hardware está, NPUs vienen en cada laptop nueva Intel y Qualcomm. La API está, Chrome la soporta hoy. Los modelos están, modelos ONNX cuantizados corren a frame rates interactivos.

Los case es de uso donde esto más importa: aplicaciones sensibles a privacidad donde los datos no pueden salir del dispositivo, aplicaciones sensibles a latencia donde los round-trips de red son inaceptables, y aplicaciones offline-capable que necesitan ML sin conectividad.

Para todo lo demás, llamar a una API sigue siendo más simple. Pero el conjunto de "todo lo demás" se está achicando rápido.