Notatka
Dostęp do tej strony wymaga autoryzacji. Może spróbować zalogować się lub zmienić katalogi.
Dostęp do tej strony wymaga autoryzacji. Możesz spróbować zmienić katalogi.
Ten przewodnik pokazuje, jak uruchamiać wnioskowanie z użyciem modelu ONNX w środowisku Electron przy użyciu powiązań JavaScript dla interfejsów API ML pakietu Zestaw SDK do aplikacji systemu Windows (wykrywanie dostawcy wykonania za pomocą ExecutionProviderCatalog oraz pobieranie modelu za pomocą ModelCatalog) w połączeniu z onnxruntime-node do wnioskowania — bez dodatku w C#. Wnioskowanie działa w pomocniczym procesie Electron, więc nie blokuje procesu głównego.
Wymagania wstępne
Przed rozpoczęciem tego przewodnika upewnij się, że:
- Ukończono konfigurację środowiska deweloperskiego.
Zainstaluj środowisko uruchomieniowe ONNX dla środowiska Node:
npm install onnxruntime-node@1.24.3
Important
Środowisko uruchomieniowe aplikacja dla systemu Windows wstępnie ładuje własne onnxruntime.dll do procesów podrzędnych. Wersja onnxruntime-node musi odpowiadać ABI ORT dołączonemu do używanej wersji Zestaw SDK do aplikacji systemu Windows. W przypadku Zestaw SDK do aplikacji systemu Windows 2.x użyj polecenia onnxruntime-node@1.24.x.
Krok 1. Potwierdzenie powiązań WinML
Pakiet Zestaw SDK do aplikacji systemu Windows ma zależność przechodnią od Microsoft.WindowsAppSDK.ML, więc interfejsy API WinML są już dostępne w wygenerowanych powiązaniach. Zweryfikuj:
Wymaga
@microsoft/dynwinrt-codegen≥0.1.0-preview.8— zobacz Pierwsze kroki z Electron, aby uzyskać informacje o rozwiązaniach zastępczych dla starszych projektów.
node -e "console.log(Object.keys(require('#winapp/bindings')).filter(k => k.startsWith('ExecutionProvider')))"
Powinieneś zobaczyć [ 'ExecutionProvider', 'ExecutionProviderCatalog', 'ExecutionProviderReadyState' ].
Krok 2. Pobieranie modelu za pośrednictwem wykazu modeli (proces główny)
Użyj ModelCatalog z powiązań JS, aby pobrać i buforować model lokalnie. Wykaz odczytuje manifest JSON (hostowany zdalnie lub lokalnie), który opisuje dostępne modele i ich adresy URL pobierania. Po pierwszym pobraniu kolejne uruchomienia używają buforowanej kopii:
Utwórz 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 };
Krok 3. Odnajdywanie i zapewnianie dostawców wykonywania (proces główny)
Użyj polecenia ExecutionProviderCatalog, aby wyświetlić listę dostępnych dostawców (CPU, DirectML, QNN/NPU), oraz polecenia ensureReadyAsync, aby w razie potrzeby pobrać ich środowisko uruchomieniowe:
Utwórz 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 };
Krok 4. Uruchamianie wnioskowania w procesie narzędziowym
Tworzenie sesji środowiska uruchomieniowego ONNX i wnioskowanie są blokowane — uruchamiaj je w procesie narzędzi elektronowym, aby zachować czas reakcji głównego procesu.
4.1. Utwórz proces roboczy
Utwórz src/winml-worker.js (ten plik jest uruchamiany w procesie narzędziowym):
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. Uruchamianie i wywoływanie procesu roboczego z poziomu głównego
Dodaj poniższe do 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. Użyj go
Upewnij się, że funkcja createWindow ma wartość async, a następnie dodaj:
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));
};
Krok 5. Uruchamianie
npx winapp node add-electron-debug-identity
npm start
W konsoli powinni być widoczni dostępni dostawcy wykonania oraz dane wyjściowe modelu.
Tip
Więcej kompleksowych przykładów wywoływania interfejsów API systemu Windows z poziomu Electrona znajdziesz w galerii „Electron on Windows”.
Dalsze kroki
Gratulacje! Używasz dostawców wykonywania WinML i środowiska uruchomieniowego ONNX z poziomu języka JavaScript — brak wymaganego dodatku języka C#. 🎉
Teraz możesz przystąpić do:
- Spakuj aplikację do dystrybucji — tworzy plik MSIX, który można wysłać.
Możesz też zapoznać się z innymi przewodnikami:
- Pokaż powiadomienie z języka JavaScript — Zestaw SDK do aplikacji systemu Windows powiadomienia za pośrednictwem powiązań JS.
- Wywoływanie interfejsów API Windows z języka JavaScript — wybierz plik przy użyciu powiązań JS.
- Wywoływanie Phi Silica w języku JavaScript — podsumowywanie tekstu przy użyciu AI w Zestaw SDK do aplikacji systemu Windows.
- Tworzenie dodatku WinML — natywnego odpowiednika dodatku języka C#.
- Wprowadzenie — wróć do głównego przewodnika.