Nota:
El acceso a esta página requiere autorización. Puede intentar iniciar sesión o cambiar directorios.
El acceso a esta página requiere autorización. Puede intentar cambiar los directorios.
En esta guía se muestra cómo ejecutar la inferencia de un modelo ONNX desde Electron mediante enlaces de JS para las API de ML de SDK de Aplicaciones para Windows (detección de proveedores de ejecución a través de ExecutionProviderCatalog y descarga del modelo mediante ModelCatalog), combinados con onnxruntime-node para la inferencia, sin necesidad de ningún complemento en C#. La inferencia se ejecuta en un proceso utilitario de Electron para que no bloquee el proceso principal.
Prerequisites
Antes de iniciar esta guía, asegúrese de que tiene:
- Completó la configuración del entorno de desarrollo.
Instale ONNX Runtime para Node:
npm install onnxruntime-node@1.24.3
Important
El Aplicación de Windows Runtime carga previamente su propio onnxruntime.dll en procesos secundarios. La versión onnxruntime-node debe coincidir con la ORT ABI incluida en su versión de SDK de Aplicaciones para Windows. Para SDK de Aplicaciones para Windows 2.x, utilice onnxruntime-node@1.24.x.
Paso 1: Confirmar vinculaciones de WinML
El SDK de Aplicaciones para Windows depende transitivamente de Microsoft.WindowsAppSDK.ML, por lo que las API de WinML ya están en los enlaces generados. Compruebe lo siguiente:
Requiere
@microsoft/dynwinrt-codegen≥0.1.0-preview.8— consulta Primeros pasos con Electron para soluciones alternativas para proyectos antiguos.
node -e "console.log(Object.keys(require('#winapp/bindings')).filter(k => k.startsWith('ExecutionProvider')))"
Deberías ver [ 'ExecutionProvider', 'ExecutionProviderCatalog', 'ExecutionProviderReadyState' ].
Paso 2: Descargar el modelo a través del catálogo de modelos (proceso principal)
Usa ModelCatalog desde los bindings de JS para descargar el modelo y almacenarlo en caché localmente. El catálogo lee un manifiesto JSON (hospedado de forma remota o local) que describe los modelos disponibles y sus direcciones URL de descarga. Después de la primera descarga, las ejecuciones posteriores usan la copia almacenada en caché:
Creación de src/winml-model.js:
const { ModelCatalog, ModelCatalogSource, Uri } = require('#winapp/bindings');
const fs = require('node:fs');
const path = require('node:path');
// Remote catalog JSON hosted in the WindowsAppSDK-Samples repo
const MODEL_CATALOG_URL =
'https://raw.githubusercontent.com/microsoft/WindowsAppSDK-Samples/main/Samples/WindowsML/Resources/SqueezeNetModelCatalog.json';
async function downloadModel(modelId, onProgress) {
const uri = Uri.createUri(MODEL_CATALOG_URL);
const source = await ModelCatalogSource.createFromUriAsync(uri);
const catalog = ModelCatalog.createInstance([source]);
const model = await catalog.findModelAsync(modelId);
const op = model.getInstanceAsync();
if (onProgress) {
op.progress((value) => {
try { onProgress(value); } catch {}
});
}
const result = await op;
const instance = result.getInstance();
if (!instance) return undefined;
const paths = instance.modelPaths;
// modelPaths returns directories containing model files
for (let i = 0; i < paths.size; i++) {
const dir = paths.getAt(i);
if (fs.existsSync(dir) && fs.statSync(dir).isDirectory()) {
const onnx = fs.readdirSync(dir).find((f) => f.endsWith('.onnx'));
if (onnx) {
instance.close();
return path.join(dir, onnx);
}
} else if (dir.endsWith('.onnx')) {
instance.close();
return dir;
}
}
instance.close();
return undefined;
}
module.exports = { downloadModel };
Paso 3: Detectar y garantizar proveedores de ejecución (proceso principal)
Use ExecutionProviderCatalog para enumerar los proveedores disponibles (CPU, DirectML, QNN/NPU) y ensureReadyAsync para descargar su tiempo de ejecución si es necesario:
Creación de src/winml-ep.js:
const { ExecutionProviderCatalog, ExecutionProviderReadyState, ExecutionProviderReadyResultState } = require('#winapp/bindings');
function listProviders() {
const catalog = ExecutionProviderCatalog.getDefault();
return catalog.findAllProviders().map((p) => ({
name: p.name,
readyState: p.readyState,
libraryPath: p.libraryPath,
}));
}
async function ensureProviderReady(providerName, onProgress) {
const catalog = ExecutionProviderCatalog.getDefault();
const providers = catalog.findAllProviders();
const provider = providers.find((p) => p.name === providerName);
if (!provider) {
throw new Error(`Execution provider not found: ${providerName}`);
}
if (provider.readyState === ExecutionProviderReadyState.Ready) {
return { name: provider.name, readyState: 'Ready', libraryPath: provider.libraryPath };
}
const op = provider.ensureReadyAsync();
if (onProgress) {
op.progress((value) => {
try { onProgress(value); } catch {}
});
}
const result = await op;
let readyState;
if (result.status === ExecutionProviderReadyResultState.Success) readyState = 'Ready';
else if (result.status === ExecutionProviderReadyResultState.Failure) readyState = 'Failed';
else readyState = 'InProgress';
return {
name: provider.name,
readyState,
diagnosticText: result.diagnosticText,
libraryPath: provider.libraryPath,
};
}
module.exports = { listProviders, ensureProviderReady };
Paso 4: Ejecución de la inferencia en un proceso de utilidad
La creación de la sesión de ONNX Runtime y la inferencia son operaciones bloqueantes; ejecútalas en un proceso de utilidad de Electron para que el proceso principal siga respondiendo.
4.1. Creación del trabajo
Crear src/winml-worker.js (este archivo se ejecuta en un proceso de utilidad):
const { roInitialize } = require('@microsoft/dynwinrt');
// When dynwinrt and onnxruntime-node share the same process, ORT's native
// init can leave the COM apartment uninitialized. Explicitly init MTA first.
roInitialize(1);
const ort = require('onnxruntime-node');
async function runModel(modelPath, inputData, inputShape, ep) {
const providers = ep === 'dml'
? [{ name: 'dml', deviceId: 0 }, 'cpu']
: ['cpu'];
const session = await ort.InferenceSession.create(modelPath, {
executionProviders: providers,
graphOptimizationLevel: 'all',
});
const inputName = session.inputNames[0];
const input = new ort.Tensor('float32', inputData, inputShape);
const outputs = await session.run({ [inputName]: input });
return Array.from(outputs[session.outputNames[0]].data);
}
process.parentPort.on('message', async (e) => {
const { id, method, args } = e.data;
try {
if (method === 'classify') {
const [modelPath, inputData, inputShape, ep] = args;
const result = await runModel(modelPath, new Float32Array(inputData), inputShape, ep);
process.parentPort.postMessage({ id, ok: true, result });
}
} catch (err) {
process.parentPort.postMessage({ id, ok: false, error: err.message });
}
});
4.2. Iniciar y llamar al trabajador desde main
Agregue lo siguiente a src/index.js:
const { utilityProcess } = require('electron');
const path = require('node:path');
const { listProviders, ensureProviderReady } = require('./winml-ep.js');
const { downloadModel } = require('./winml-model.js');
let worker = null;
let workerReady = null;
const pending = new Map();
let nextId = 1;
function startWinmlWorker() {
worker = utilityProcess.fork(path.join(__dirname, 'winml-worker.js'), [], {
stdio: 'pipe',
serviceName: 'winml-worker',
});
worker.on('message', (msg) => {
const entry = pending.get(msg.id);
if (!entry) return;
pending.delete(msg.id);
if (msg.ok) entry.resolve(msg.result);
else entry.reject(new Error(msg.error));
});
workerReady = new Promise((resolve) => worker.once('spawn', resolve));
}
async function classify(modelPath, inputData, inputShape, ep) {
if (!worker) startWinmlWorker();
await workerReady;
return new Promise((resolve, reject) => {
const id = nextId++;
pending.set(id, { resolve, reject });
worker.postMessage({ id, method: 'classify', args: [modelPath, Array.from(inputData), inputShape, ep] });
});
}
4.3. Úsalo
Asegúrese de que la createWindow función es asyncy agregue:
const createWindow = async () => {
// ... existing window creation code ...
// List and ensure all execution providers are ready
const providers = listProviders();
console.log('Available providers:', providers);
for (const ep of providers) {
console.log(`Ensuring ${ep.name} is ready...`);
const result = await ensureProviderReady(ep.name, (progress) => {
const pct = progress <= 1 ? Math.round(progress * 100) : Math.round(progress);
process.stdout.write(`\r ${ep.name}: ${pct}%`);
});
process.stdout.write('\n');
console.log(` ${ep.name}: ${result.readyState}`);
}
// Download model via Model Catalog (cached after first run)
console.log('Downloading model...');
const modelPath = await downloadModel('squeezenet', (progress) => {
if (progress >= 0 && progress <= 100) {
process.stdout.write(`\rDownloading model: ${Math.round(progress)}%`);
}
});
process.stdout.write('\n');
console.log('Model path:', modelPath);
// Run inference in utility process (replace with real preprocessed data)
const inputData = new Float32Array(1 * 3 * 224 * 224);
const output = await classify(modelPath, inputData, [1, 3, 224, 224], 'dml');
console.log('Model output (top 5 values):', output.slice(0, 5));
};
Paso 5: Ejecutarlo
npx winapp node add-electron-debug-identity
npm start
Debería ver los proveedores de ejecución disponibles y la salida del modelo en la consola.
Tip
Para obtener una canalización de clasificación de imágenes completa con descodificación de imágenes a través de enlaces JS (StorageFile, BitmapDecoder, BitmapTransform), consulte el ejemplo winML de la Galería de Electrones.
Pasos siguientes
¡Felicidades! Está ejecutando proveedores de ejecución de WinML y ONNX Runtime desde JavaScript: no se requiere ningún complemento de C#. 🎉
Ahora ya está listo para:
- Empaquetar la aplicación para la distribución : genera un MSIX que puedes enviar.
O explore otras guías:
- Mostrar una notificación desde JavaScript — notificaciones de SDK de Aplicaciones para Windows mediante vinculaciones para JavaScript.
- Llama a las API de Windows desde JavaScript — elige un archivo mediante enlaces de JS.
- Llamar a Phi Silica desde JavaScript — resumir texto con la IA de SDK de Aplicaciones para Windows.
- Creación de un complemento winML : homólogo nativo del complemento de C#.
- Introducción: vuelva a la guía principal.