Kommentar
Åtkomst till den här sidan kräver auktorisering. Du kan prova att logga in eller ändra kataloger.
Åtkomst till den här sidan kräver auktorisering. Du kan prova att ändra kataloger.
Den här guiden visar hur du kör ONNX-modellinferens från Electron med hjälp av JS-bindningar för Windows App SDK ML-API:er (identifiering av körningsprovider via ExecutionProviderCatalog och modellnedladdning via ModelCatalog) kombinerat med onnxruntime-node för slutsatsdragning – inget C#-tillägg krävs. Inferens körs i en hjälpprocess i Electron så att huvudprocessen inte blockeras.
Förutsättningar
Kontrollera att du har gjort så här innan du startar den här guiden:
- Installationen av utvecklingsmiljön har slutförts.
Installera ONNX Runtime för Node:
npm install onnxruntime-node@1.24.3
Important
Windows App Runtime förinläser sin egen onnxruntime.dll i underprocesser. Versionen onnxruntime-node måste matcha ORT ABI som paketeras med din Windows App SDK version. För Windows App SDK 2.x använder du onnxruntime-node@1.24.x.
Steg 1: Bekräfta WinML-bindningar
Windows App SDK är transitivt beroende av Microsoft.WindowsAppSDK.ML, så WinML-API:erna finns redan i dina genererade bindningar. Verifiera följande:
Kräver
@microsoft/dynwinrt-codegen≥0.1.0-preview.8— se Kom igång med Electron för reservlösningar för äldre projekt.
node -e "console.log(Object.keys(require('#winapp/bindings')).filter(k => k.startsWith('ExecutionProvider')))"
Du bör se [ 'ExecutionProvider', 'ExecutionProviderCatalog', 'ExecutionProviderReadyState' ].
Steg 2: Ladda ned modellen via modellkatalogen (huvudprocess)
Använd ModelCatalog från JavaScript-bindningar för att ladda ned och lagra modellen i cache lokalt. Katalogen läser ett JSON-manifest (fjärrhanterat eller lokalt) som beskriver tillgängliga modeller och deras nedladdnings-URL:er. Efter den första nedladdningen använder efterföljande körningar den cachelagrade kopian:
Skapa 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 };
Steg 3: Upptäck och säkerställ körningsleverantörer (huvudprocess)
Använd ExecutionProviderCatalog för att lista tillgängliga leverantörer (CPU, DirectML, QNN/NPU) och ensureReadyAsync för att ladda ned deras körning om det behövs:
Skapa 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 };
Steg 4: Köra slutsatsdragning i en verktygsprocess
Att skapa ONNX Runtime-sessioner och köra inferens är blockerande – kör dem i en hjälpprocess i Electron för att hålla huvudprocessen responsiv.
4.1. Skapa arbetaren
Skapa src/winml-worker.js (den här filen körs i en verktygsprocess):
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. Starta och anropa arbetaren från main
Lägg till följande i din 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. Använd den
Kontrollera att funktionen createWindow är asyncoch lägg sedan till:
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));
};
Steg 5: Kör det
npx winapp node add-electron-debug-identity
npm start
Du bör se tillgängliga körningsproviders och modellutdata i konsolen.
Tip
En fullständig pipeline för bildklassificering med avkodning av bilder via JS-bindningar (StorageFile, , BitmapDecoderBitmapTransform), finns i WinML-exemplet för Electron Gallery.
Nästa steg
Grattis! Du kör WinML-exekveringsleverantörer och ONNX Runtime i JavaScript – inget C#-tillägg krävs. 🎉
Nu är du redo att:
- Paketera din app för distribution – skapa en MSIX som du kan skicka.
Eller utforska andra guider:
- Visa ett meddelande från JavaScript – Windows App SDK meddelanden via JS-bindningar.
- Anropa Windows API:er från JavaScript – välj en fil med JS-bindningar.
- Anropa Phi Silica från JavaScript – sammanfatta text med Windows App SDK AI.
- Skapa en WinML Addon – inbyggd C#-tilläggsmotsvarighet.
- Översikt över komma igång – gå tillbaka till huvudguiden.
Windows developer