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.