JavaScript'ten WinML çalıştırma (JS bağlamaları)

Bu kılavuzda, Windows Uygulama SDK'sı ML API'leri için JS bağlamalarını kullanarak Electron'dan ONNX model çıkarımının nasıl çalıştırıldığı (yürütme sağlayıcısı bulma ExecutionProviderCatalog ve aracılığıyla model indirmeModelCatalog) onnxruntime-node ile birlikte çıkarım için nasıl çalıştırıldığı gösterilmektedir; C# eklentisi gerekmez. Çıkarım bir Elektron yardımcı programı işleminde çalıştırılır, böylece ana işlemi engellemez.

Prerequisites

Bu kılavuza başlamadan önce şunları yaptığınızdan emin olun:

Node için ONNX Runtime'ı yükleyin:

npm install onnxruntime-node@1.24.3

Important

Windows Uygulaması Runtime, kendi onnxruntime.dll bileşenini alt işlemlere önceden yükler. Sürüm, onnxruntime-node Windows Uygulama SDK'sı sürümünüzle birlikte gelen ORT ABI ile eşleşmelidir. Windows Uygulama SDK'sı 2.x için kullanınonnxruntime-node@1.24.x.

1. Adım: WinML bağlamalarını onaylama

Windows Uygulama SDK'sı’nın Microsoft.WindowsAppSDK.ML için geçişli bağımlılığı vardır; bu nedenle WinML API’leri zaten oluşturulan bağlamalarınızda yer alır. Şunu doğrulayın:

@microsoft/dynwinrt-codegen0.1.0-preview.8 gerektirir — eski projeler için alternatifler için Electron ile çalışmaya başlama konusuna bakın.

node -e "console.log(Object.keys(require('#winapp/bindings')).filter(k => k.startsWith('ExecutionProvider')))"

ifadesini görmeniz [ 'ExecutionProvider', 'ExecutionProviderCatalog', 'ExecutionProviderReadyState' ]gerekir.

2. Adım: Modeli Model Kataloğu aracılığıyla indirme (ana işlem)

Modeli yerel olarak indirmek ve önbelleğe almak için JS bağlamalarından kullanın ModelCatalog . Katalog, kullanılabilir modelleri ve bunların indirme URL'lerini açıklayan bir JSON bildirimini (uzaktan veya yerel olarak barındırılan) okur. İlk indirmeden sonra, sonraki çalıştırmalar önbelleğe alınmış kopyayı kullanır:

Oluştur 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 };

3. Adım: Yürütme sağlayıcılarını bulma ve sağlama (ana işlem)

Kullanılabilir sağlayıcıları (CPU, DirectML, QNN/NPU) listelemek için ExecutionProviderCatalog, gerekirse çalışma zamanlarını indirmek içinse ensureReadyAsync kullanın:

Oluştur 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 };

4. Adım: Bir yardımcı program işleminde çıkarım çalıştırma

ONNX Runtime oturumu oluşturma ve çıkarım işlemleri bloklayıcıdır — ana sürecin yanıt vermeye devam etmesini sağlamak için bunları bir Electron yardımcı işleminde çalıştırın.

4.1. Çalışanı oluşturma

Oluştur src/winml-worker.js (bu dosya bir yardımcı program işleminde çalışır):

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. Çalışana ana bilgisayardan başlatma ve çağırma

Lütfen aşağıdakileri src/index.js'a ekleyin:

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. Bunu kullanın

İşlevinizin createWindow olduğundan asyncemin olun ve şunları ekleyin:

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));
};

5. Adım: Çalıştırma

npx winapp node add-electron-debug-identity
npm start

Konsolda kullanılabilir yürütme sağlayıcılarını ve model çıkışını görmeniz gerekir.

Tip

JS bağlamaları (StorageFile, BitmapDecoder, BitmapTransform) aracılığıyla görüntü kod çözme ile eksiksiz bir görüntü sınıflandırma işlem hattı için bkz. Elektron Galerisi WinML örneği.

Sonraki Adımlar

Tebrikler! JavaScript'ten WinML yürütme sağlayıcıları ve ONNX Çalışma Zamanı çalıştırıyorsunuz; C# eklentisi gerekmez. 🎉

Artık şunu yapmaya hazırsınız:

Veya diğer kılavuzları keşfedin: