Nota
L'accesso a questa pagina richiede l'autorizzazione. È possibile provare ad accedere o modificare le directory.
L'accesso a questa pagina richiede l'autorizzazione. È possibile provare a modificare le directory.
Questa guida illustra come eseguire l'inferenza del modello ONNX da Electron usando associazioni JS per le API ML SDK per app di Windows (individuazione del provider di esecuzione tramite ExecutionProviderCatalog e download di modelli tramite ModelCatalog) combinate con onnxruntime-node per l'inferenza, senza che sia necessario alcun componente aggiuntivo C#. L'inferenza viene eseguita in un processo di utilità Electron in modo che non blocchi il processo principale.
Prerequisiti
Prima di iniziare questa guida, assicurarsi di avere:
- Completata la configurazione dell'ambiente di sviluppo.
Installare il runtime ONNX per Node:
npm install onnxruntime-node@1.24.3
Important
app di Windows Runtime precarica il proprio onnxruntime.dll nei processi figlio. La versione onnxruntime-node deve corrispondere all'ABI ORT inclusa nella versione di SDK per app di Windows. Per SDK per app di Windows 2.x, usare onnxruntime-node@1.24.x.
Passaggio 1: Conferma i binding di WinML
Il SDK per app di Windows in modo transitivo dipende da Microsoft.WindowsAppSDK.ML, quindi le API WinML sono già presenti nelle associazioni generate. Verificare:
Richiede
@microsoft/dynwinrt-codegen≥0.1.0-preview.8: vedere Introduzione a Electron per i fallback del progetto meno recenti.
node -e "console.log(Object.keys(require('#winapp/bindings')).filter(k => k.startsWith('ExecutionProvider')))"
Dovresti vedere [ 'ExecutionProvider', 'ExecutionProviderCatalog', 'ExecutionProviderReadyState' ].
Passaggio 2: Scaricare il modello tramite catalogo modelli (processo principale)
Usare ModelCatalog da associazioni JS per scaricare e memorizzare nella cache il modello in locale. Il catalogo legge un manifesto JSON (ospitato in modalità remota o locale) che descrive i modelli disponibili e i relativi URL di download. Dopo il primo download, le esecuzioni successive usano la copia memorizzata nella cache:
Creare 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 };
Passaggio 3: Individuare e verificare i provider di esecuzione (processo principale)
Usare ExecutionProviderCatalog per elencare i provider disponibili (CPU, DirectML, QNN/NPU) e ensureReadyAsync per scaricare il runtime, se necessario:
Creare 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 };
Passaggio 4: Eseguire l'inferenza in un processo di utilità
La creazione della sessione e l'inferenza di ONNX Runtime sono operazioni bloccanti: eseguirle in un processo utility di Electron per mantenere il processo principale reattivo.
4.1. Creare il ruolo di lavoro
Crea src/winml-worker.js (questo file viene eseguito in un processo di utilità):
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. Avviare e chiamare il ruolo di lavoro dal server principale
Aggiungere quanto segue 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. Usalo
Assicurarsi che la createWindow funzione sia async, quindi aggiungere:
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));
};
Passaggio 5: Eseguilo
npx winapp node add-electron-debug-identity
npm start
Nella console dovrebbero essere visualizzati i provider di esecuzione disponibili e l'output del modello.
Tip
Per una pipeline completa di classificazione delle immagini con decodifica delle immagini tramite associazioni JS (StorageFile, BitmapDecoder, BitmapTransform), vedere l'esempio WinML di Electron Gallery.
Operazioni successive
Congratulazioni! Si eseguono provider di esecuzione WinML e ONNX Runtime da JavaScript: nessun componente aggiuntivo C# richiesto. 🎉
A questo momento è possibile:
- Crea il pacchetto della tua app per la distribuzione — crea un pacchetto MSIX pronto per la distribuzione.
In alternativa, esplorare altre guide:
- Mostrare una notifica da JavaScript — notifiche di SDK per app di Windows tramite binding JS.
- Chiamare Windows API da JavaScript: selezionare un file usando associazioni JS.
- Chiamare Phi Silica da JavaScript — riassumere il testo con l'IA di SDK per app di Windows.
- Creazione di un componente aggiuntivo WinML — controparte nativa in C#.
- Panoramica introduttiva : tornare alla guida principale.