Catatan
Akses ke halaman ini memerlukan otorisasi. Anda dapat mencoba masuk atau mengubah direktori.
Akses ke halaman ini memerlukan otorisasi. Anda dapat mencoba mengubah direktori.
Panduan ini menunjukkan cara menjalankan inferensi model ONNX dari Electron menggunakan binding JS untuk API ML SDK Aplikasi Windows (penemuan penyedia eksekusi melalui ExecutionProviderCatalog dan pengunduhan model melalui ModelCatalog) yang dikombinasikan dengan onnxruntime-node untuk inferensi — tidak memerlukan add-on C#. Inferensi dijalankan di proses utilitas Electron sehingga tidak memblokir proses utama.
Prerequisites
Sebelum memulai panduan ini, pastikan Anda telah:
- Menyelesaikan penyiapan lingkungan pengembangan.
Instal ONNX Runtime untuk Node:
npm install onnxruntime-node@1.24.3
Important
Windows App Runtime memuat terlebih dahulu onnxruntime.dll miliknya sendiri ke proses turunan. Versi onnxruntime-node harus cocok dengan ORT ABI yang dibundel dengan versi SDK Aplikasi Windows Anda. Untuk SDK Aplikasi Windows 2.x, gunakan onnxruntime-node@1.24.x.
Langkah 1: Mengonfirmasi pengikatan WinML
SDK Aplikasi Windows secara transitif tergantung pada Microsoft.WindowsAppSDK.ML, sehingga API WinML sudah ada dalam pengikatan yang Anda buat. Verifikasi:
Memerlukan
@microsoft/dynwinrt-codegen≥0.1.0-preview.8— lihat Memulai dengan Electron untuk alternatif bagi proyek lama.
node -e "console.log(Object.keys(require('#winapp/bindings')).filter(k => k.startsWith('ExecutionProvider')))"
Anda seharusnya melihat [ 'ExecutionProvider', 'ExecutionProviderCatalog', 'ExecutionProviderReadyState' ].
Langkah 2: Unduh model melalui Katalog Model (proses utama)
Gunakan ModelCatalog dari pengikatan JS untuk mengunduh dan menyimpan cache model secara lokal. Katalog membaca manifes JSON (dihosting dari jarak jauh atau lokal) yang menjelaskan model yang tersedia dan URL unduhannya. Setelah pengunduhan pertama, eksekusi berikutnya menggunakan salinan yang di-cache:
Buat 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 };
Langkah 3: Temukan dan pastikan penyedia eksekusi (proses utama)
Gunakan ExecutionProviderCatalog untuk mencantumkan penyedia yang tersedia (CPU, DirectML, QNN/NPU) dan ensureReadyAsync untuk mengunduh runtime mereka jika diperlukan:
Buat 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 };
Langkah 4: Jalankan inferensi dalam proses utilitas
Pembuatan sesi dan inferensi ONNX Runtime bersifat memblokir — jalankan keduanya di proses utilitas Electron untuk menjaga proses utama tetap responsif.
4.1. Membuat pekerja
Buat src/winml-worker.js (file ini berjalan dalam proses utilitas):
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. Luncurkan dan panggil pekerja dari utama
Tambahkan yang berikut ini ke 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. Gunakan itu
Pastikan fungsi Anda createWindow adalah async, lalu tambahkan:
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));
};
Langkah 5: Jalankan
npx winapp node add-electron-debug-identity
npm start
Anda akan melihat penyedia eksekusi dan output model yang tersedia di konsol.
Tip
Untuk contoh end-to-end lainnya tentang pemanggilan API Windows dari Electron, lihat galeri Electron di Windows.
Langkah Selanjutnya
Selamat! Anda menjalankan penyedia eksekusi WinML dan ONNX Runtime dari JavaScript — tidak diperlukan addon C#. 🎉
Sekarang Anda siap untuk:
- Kemas Aplikasi Anda untuk Distribusi — hasilkan MSIX yang dapat Anda kirim.
Atau jelajahi panduan lain:
- Tampilkan Pemberitahuan dari JavaScript — SDK Aplikasi Windows pemberitahuan melalui pengikatan JS.
- Panggil API Windows dari JavaScript — pilih file menggunakan pengikatan JS.
- Panggil Phi Silica dari JavaScript — ringkas teks dengan AI SDK Aplikasi Windows.
- Membuat Addon WinML — mitra addon C# asli.
- Ringkasan Memulai — kembali ke panduan utama.
Windows developer