Remarque
L’accès à cette page nécessite une autorisation. Vous pouvez essayer de vous connecter ou de modifier des répertoires.
L’accès à cette page nécessite une autorisation. Vous pouvez essayer de modifier des répertoires.
Ce guide montre comment exécuter l’inférence de modèle ONNX à partir d’Electron à l’aide de liaisons JS pour SDK d'application Windows API ML (découverte du fournisseur d’exécution via ExecutionProviderCatalog et téléchargement de modèle viaModelCatalog) combinées avec onnxruntime-node pour l’inférence — aucun module complémentaire C# n’est requis. L’inférence s’exécute dans un processus utilitaire Electron afin qu’elle ne bloque pas le processus principal.
Prerequisites
Avant de commencer ce guide, vérifiez que vous avez :
Installez ONNX Runtime pour Node :
npm install onnxruntime-node@1.24.3
Important
Le runtime de application Windows précharge son propre onnxruntime.dll dans les processus enfants. La onnxruntime-node version doit correspondre à l’ABI ORT groupée avec votre version de SDK d'application Windows. Pour SDK d'application Windows 2.x, utilisez onnxruntime-node@1.24.x.
Étape 1 : Confirmer les liaisons WinML
La SDK d'application Windows transitive dépend de Microsoft.WindowsAppSDK.ML, de sorte que les API WinML se trouvent déjà dans vos liaisons générées. Vérifier :
Nécessite
@microsoft/dynwinrt-codegen≥0.1.0-preview.8— consultez Prise en main d’Electron pour les solutions de repli des projets plus anciens.
node -e "console.log(Object.keys(require('#winapp/bindings')).filter(k => k.startsWith('ExecutionProvider')))"
Vous devriez voir [ 'ExecutionProvider', 'ExecutionProviderCatalog', 'ExecutionProviderReadyState' ].
Étape 2 : Télécharger le modèle via le catalogue de modèles (processus principal)
Utilisez ModelCatalog des liaisons JS pour télécharger et mettre en cache le modèle localement. Le catalogue lit un manifeste JSON (hébergé à distance ou localement) qui décrit les modèles disponibles et leurs URL de téléchargement. Après le premier téléchargement, les exécutions suivantes utilisent la copie mise en cache :
Créez 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 };
Étape 3 : Découvrir et garantir les fournisseurs d’exécution (processus principal)
Permet ExecutionProviderCatalog de répertorier les fournisseurs disponibles (PROCESSEUR, DirectML, QNN/NPU) et ensureReadyAsync de télécharger leur runtime si nécessaire :
Créez 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 };
Étape 4 : Exécuter l’inférence dans un processus utilitaire
La création d’une session ONNX Runtime et l’inférence sont bloquantes : exécutez-les dans un processus utilitaire d’Electron pour maintenir le processus principal réactif.
4.1. Créer le worker
Créez src/winml-worker.js (ce fichier s’exécute dans un processus utilitaire) :
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. Lancer et appeler le worker depuis la fonction main
Ajoutez ce qui suit à votre 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. Utilisez-le
Vérifiez que votre createWindow fonction est async, puis ajoutez :
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));
};
Étape 5 : l’exécuter
npx winapp node add-electron-debug-identity
npm start
Vous devez voir les fournisseurs d’exécution disponibles et la sortie du modèle dans la console.
Tip
Pour obtenir un pipeline complet de classification d’images avec décodage d’images via des liaisons JS (StorageFile, BitmapDecoder, BitmapTransform), consultez l’exemple WinML de la galerie Electron.
Prochaines étapes
Félicitations ! Vous exécutez des fournisseurs d’exécution WinML et onNX Runtime à partir de JavaScript , aucun module complémentaire C# n’est requis. 🎉
Vous êtes maintenant prêt à :
- Empaqueter votre application pour la distribution : produisez un MSIX que vous pouvez expédier.
Ou explorez d’autres guides :
- Afficher une notification à partir de JavaScript : SDK d'application Windows notifications par le biais de liaisons JS.
- Appelez Windows API à partir de JavaScript : choisissez un fichier à l’aide de liaisons JS.
- Appelez Phi Silicon à partir de JavaScript : résumez le texte avec SDK d'application Windows IA.
- Création d’un complément WinML : équivalent natif du module complémentaire C#.
- Vue d’ensemble de la prise en main : revenez au guide principal.
Windows developer