Verwendung des WebNN Execution Providers

Dieses Dokument erklärt, wie der WebNN Execution Provider in ONNX Runtime verwendet wird.

Inhalt

Grundlagen

Was ist WebNN? Sollte ich es verwenden?

Web Neural Network (WebNN) API ist ein neuer Web-Standard, der es Web-Anwendungen und Frameworks ermöglicht, Deep-Neural-Networks mit On-Device-Hardware wie GPUs, CPUs oder speziell entwickelten KI-Beschleunigern (NPUs) zu beschleunigen.

WebNN ist in den neuesten Versionen von Chrome und Edge unter Windows, Linux, macOS, Android und ChromeOS hinter der Flagge "Enables WebNN API" verfügbar. Informieren Sie sich im WebNN-Status über den aktuellen Implementierungsstand.

Beziehen Sie sich auf die WebNN-Operatoren für den aktuellsten Stand der Operator-Unterstützung im WebNN Execution Provider. Wenn der WebNN Execution Provider die meisten Operatoren in Ihrem Modell unterstützt (wobei nicht unterstützte Operatoren auf den WASM EP zurückfallen) und Sie eine energieeffiziente, schnellere Verarbeitung und reibungslosere Leistung durch die Nutzung von On-Device-Beschleunigern erzielen möchten, sollten Sie die Verwendung des WebNN Execution Providers in Erwägung ziehen.

Wie verwende ich den WebNN EP in ONNX Runtime Web

In diesem Abschnitt wird davon ausgegangen, dass Sie Ihre Webanwendung bereits mit ONNX Runtime Web eingerichtet haben. Wenn nicht, können Sie die Ersten Schritte für grundlegende Informationen aufrufen.

Um den WebNN EP zu verwenden, müssen Sie nur 3 kleine Änderungen vornehmen

  1. Aktualisieren Sie Ihre Importanweisung

    • Ändern Sie für das HTML-Skript-Tag ort.min.js in ort.all.min.js
      <script src="https://example.com/path/ort.all.min.js"></script>
      
    • Ändern Sie für die JavaScript-Importanweisung onnxruntime-web in onnxruntime-web/all
      import * as ort from 'onnxruntime-web/all';
      

    Details finden Sie unter Bedingte Importierung.

  2. Geben Sie 'webnn' EP explizit in den Session-Optionen an
    const session = await ort.InferenceSession.create(modelPath, { ..., executionProviders: ['webnn'] });
    

    Der WebNN EP bietet auch eine Reihe von Optionen für die Erstellung verschiedener Arten von WebNN MLContext.

    • deviceType: 'cpu'|'gpu'|'npu' (Standardwert ist 'cpu'), gibt den bevorzugten Gerätetyp für den MLContext an.
    • powerPreference: 'default'|'low-power'|'high-performance' (Standardwert ist 'default'), gibt den bevorzugten Stromverbrauchstyp für den MLContext an.
    • context: Typ von MLContext, ermöglicht es Benutzern, einen voreingten MLContext an den WebNN EP zu übergeben, dies ist für die IO-Binding-Funktion erforderlich. Wenn diese Option angegeben wird, werden die anderen Optionen ignoriert.

    Beispiel für die Verwendung von WebNN EP-Optionen

    const options = {
       executionProviders: [
         {
           name: 'webnn',
           deviceType: 'gpu',
           powerPreference: "default",
         },
       ],
    }
    
  3. Wenn es sich um ein dynamisches Shape-Modell handelt, bietet ONNX Runtime Web die Session-Option freeDimensionOverrides, um die freien Dimensionen des Modells zu überschreiben. Weitere Details finden Sie unter freeDimensionOverrides Einführung.

Die WebNN API und der WebNN EP werden aktiv entwickelt. Möglicherweise möchten Sie die neueste Nightly Build-Version von ONNX Runtime Web (onnxruntime-web@dev) installieren, um von den neuesten Funktionen und Verbesserungen zu profitieren.

Tensor-Daten auf WebNN MLTensor beibehalten (IO Binding)

Standardmäßig sind die Ein- und Ausgaben eines Modells Tensoren, die Daten im CPU-Speicher halten. Wenn Sie eine Session mit dem WebNN EP mit dem Gerätetyp 'gpu' oder 'npu' ausführen, werden die Daten auf den GPU- oder NPU-Speicher kopiert und die Ergebnisse zurück in den CPU-Speicher kopiert. Speicher kopieren zwischen verschiedenen Geräten sowie zwischen verschiedenen Sessions verursacht viel Overhead bei der Inferenzzeit. WebNN bietet einen neuen, gerätespezifischen Speichertyp, MLTensor, um dieses Problem zu lösen. Wenn Sie Ihre Eingabedaten aus einem MLTensor erhalten oder die Ausgabedaten für die weitere Verarbeitung auf einem MLTensor behalten möchten, können Sie IO Binding verwenden, um die Daten auf dem MLTensor zu belassen. Dies ist besonders hilfreich beim Ausführen von Transformer-basierten Modellen, die in der Regel ein einzelnes Modell mehrmals mit der vorherigen Ausgabe als nächste Eingabe ausführen.

Für Modelleingaben gilt: Wenn Ihre Eingabedaten ein WebNN-Speicher-MLTensor sind, können Sie einen MLTensor-Tensor erstellen und ihn als Eingabetensor verwenden.

Für Modellausgaben gibt es 2 Möglichkeiten, die IO-Binding-Funktion zu nutzen

Bitte beachten Sie auch das folgende Thema

Hinweis: Der MLTensor erfordert einen gemeinsamen MLContext für IO Binding. Dies bedeutet, dass der MLContext als WebNN EP-Option voreingestellt und über alle Sessions hinweg verwendet werden sollte.

Eingabetensor aus einem MLTensor erstellen

Wenn Ihre Eingabedaten ein WebNN-Speicher-MLTensor sind, können Sie einen MLTensor-Tensor erstellen und ihn als Eingabetensor verwenden

// Create WebNN MLContext
const mlContext = await navigator.ml.createContext({deviceType, ...});
// Create a WebNN MLTensor
const inputMLTensor = await mlContext.createTensor({
  dataType: 'float32',
  shape: [1, 3, 224, 224],
  writable: true,
});
// Write data to the MLTensor
const inputArrayBuffer = new Float32Array(1 * 3 * 224 * 224).fill(1.0);
mlContext.writeTensor(inputMLTensor, inputArrayBuffer);

// Create an ORT tensor from the MLTensor
const inputTensor = ort.Tensor.fromMLTensor(inputMLTensor, {
  dataType: 'float32',
  dims: [1, 3, 224, 224],
});

Verwenden Sie diesen Tensor als Modelleingaben (Feeds), damit die Eingabedaten auf dem MLTensor verbleiben.

Vorkonfigurierte MLTensor-Tensoren verwenden

Wenn Sie die Ausgabeform im Voraus kennen, können Sie einen MLTensor-Tensor erstellen und ihn als Ausgabetensor verwenden


// Create a pre-allocated MLTensor and the corresponding ORT tensor. Assuming that the output shape is [10, 1000].
const mlContext = await navigator.ml.createContext({deviceType, ...});
const preallocatedMLTensor = await mlContext.createTensor({
  dataType: 'float32',
  shape: [10, 1000],
  readable: true,
});

const preallocatedOutputTensor = ort.Tensor.fromMLTensor(preallocatedMLTensor, {
  dataType: 'float32',
  dims: [10, 1000],
});

// ...

// Run the session with fetches
const feeds = { 'input_0': inputTensor };
const fetches = { 'output_0': preallocatedOutputTensor };
await session.run(feeds, fetches);

// Read output_0 data from preallocatedMLTensor if need
const output_0 = await mlContext.readTensor(preallocatedMLTensor);
console.log('output_0 value:', new Float32Array(output_0));

Durch die Angabe des Ausgabetensors in den Abrufen verwendet ONNX Runtime Web den vorkonfigurierten MLTensor als Ausgabetensor. Bei einer Forminkonsistenz schlägt der run()-Aufruf fehl.

Speicherort der Ausgabedaten festlegen

Wenn Sie keine vorkonfigurierten MLTensor-Tensoren für Ausgaben verwenden möchten, können Sie den Speicherort der Ausgabedaten auch in den Session-Optionen festlegen

const sessionOptions1 = {
  ...,
  // keep all output data on MLTensor
  preferredOutputLocation: 'ml-tensor'
};

const sessionOptions2 = {
  ...,
  // alternatively, you can specify the output location for each output tensor
  preferredOutputLocation: {
    'output_0': 'cpu',         // keep output_0 on CPU. This is the default behavior.
    'output_1': 'ml-tensor'   // keep output_1 on MLTensor tensor
  }
};

// ...

// Run the session
const feeds = { 'input_0': inputTensor };
const results = await session.run(feeds);

// Read output_1 data
const output_1 = await results['output_1'].getData();
console.log('output_1 value:', new Float32Array(output_1));

Durch Angabe der Konfiguration preferredOutputLocation behält ONNX Runtime Web die Ausgabedaten auf dem angegebenen Gerät.

Details finden Sie in der API-Referenz: preferredOutputLocation.

Hinweise

Lebenszyklusverwaltung von MLTensor-Tensoren

Es ist wichtig zu verstehen, wie der zugrunde liegende MLTensor verwaltet wird, um Speicherlecks zu vermeiden und die Effizienz der Tensor-Nutzung zu verbessern.

Ein MLTensor-Tensor wird entweder durch Benutzercode oder durch ONNX Runtime Web als Modellausgabe erstellt.

  • Wenn er durch Benutzercode erstellt wird, wird er immer mit einem vorhandenen MLTensor über Tensor.fromMLTensor() erstellt. In diesem Fall "besitzt" der Tensor den MLTensor nicht.

    • Es liegt in der Verantwortung des Benutzers, sicherzustellen, dass der zugrunde liegende MLTensor während der Inferenz gültig ist, und mlTensor.destroy() aufzurufen, um den MLTensor freizugeben, wenn er nicht mehr benötigt wird.
    • Vermeiden Sie den Aufruf von tensor.getData() und tensor.dispose(). Verwenden Sie den MLTensor-Tensor direkt.
    • Die Verwendung eines MLTensor-Tensors mit einem zerstörten MLTensor führt zum Fehlschlag der Session-Ausführung.
  • Wenn er von ONNX Runtime Web als Modellausgabe erstellt wird (kein vorkonfigurierter MLTensor-Tensor), "besitzt" der Tensor den MLTensor.

    • Sie müssen sich keine Sorgen machen, dass der MLTensor zerstört wird, bevor der Tensor verwendet wird.
    • Rufen Sie tensor.getData() auf, um die Daten vom MLTensor auf die CPU herunterzuladen und die Daten als typisiertes Array zu erhalten.
    • Rufen Sie explizit tensor.dispose() auf, um den zugrunde liegenden MLTensor zu zerstören, wenn er nicht mehr benötigt wird.