Ausführen mehrerer ML-Modelle in einer Kette

Windows ML unterstützt das hochleistungsfähige Laden und Ausführen von Modellketten durch sorgfältige Optimierung des GPU-Pfads. Modellketten werden durch mindestens zwei Modelle definiert, die nacheinander ausgeführt werden, wobei die Ergebnisse eines Modells zu den Eingaben für das nächste Modell in der Kette werden.

Um zu erklären, wie Modelle mit Windows ML effizient verkettet werden können, verwenden wir ein ONNX-Modell mit FNS-Candy-Stilübertragung als Beispiel. Diese Art von Modell finden Sie im Beispielordner „FNS-Candy Style Transfer“ auf GitHub.

Nehmen wir an, wir möchten eine Kette ausführen, die aus zwei Instanzen desselben FNS-Candy-Modells besteht, das hier den Namen mosaic.onnx trägt. Der Anwendungscode übergibt ein Bild an das erste Modell in der Kette, lässt es die Ausgaben berechnen und übergibt dann das transformierte Bild an eine andere Instanz von FNS-Candy, wodurch das endgültige Bild generiert wird.

Die folgenden Schritte veranschaulichen, wie Sie dies mithilfe von Windows ML erreichen können.

Hinweis

In einem Szenario der realen Welt würden Sie höchstwahrscheinlich zwei verschiedene Modelle verwenden, aber das Beispiel sollte ausreichen, um die Konzepte zu veranschaulichen.

  1. Zunächst laden wir das Modell mosaic.onnx, damit wir es verwenden können.
std::wstring filePath = L"path\\to\\mosaic.onnx"; 
LearningModel model = LearningModel::LoadFromFilePath(filePath);
string filePath = "path\\to\\mosaic.onnx";
LearningModel model = LearningModel.LoadFromFilePath(filePath);
  1. Dann erstellen wir zwei identische Sitzungen auf der Standard-GPU des Geräts, indem das gleiche Modell als Eingabeparameter verwendet wird.
LearningModelSession session1(model, LearningModelDevice(LearningModelDeviceKind::DirectX));
LearningModelSession session2(model, LearningModelDevice(LearningModelDeviceKind::DirectX));
LearningModelSession session1 = 
  new LearningModelSession(model, new LearningModelDevice(LearningModelDeviceKind.DirectX));
LearningModelSession session2 = 
  new LearningModelSession(model, new LearningModelDevice(LearningModelDeviceKind.DirectX));

Hinweis

Um die Leistungsvorteile der Verkettung zu nutzen, müssen Sie für alle Modelle identische GPU-Sitzungen erstellen. Wenn dies nicht der Fall ist, führt dies zu einer zusätzlichen Datenverschiebung aus der GPU in die CPU, was die Leistung beeinträchtigen würde.

  1. In den folgenden Codezeilen werden Bindungen für jede Sitzung erstellt:
LearningModelBinding binding1(session1);
LearningModelBinding binding2(session2);
LearningModelBinding binding1 = new LearningModelBinding(session1);
LearningModelBinding binding2 = new LearningModelBinding(session2);
  1. Nun binden wir eine Eingabe für unser erstes Modell. Wir übergeben ein Bild, das sich im gleichen Pfad wie unser Modell befindet. In diesem Beispiel heißt das Bild „fish_720.png“.
//get the input descriptor
ILearningModelFeatureDescriptor input = model.InputFeatures().GetAt(0);
//load a SoftwareBitmap
hstring imagePath = L"path\\to\\fish_720.png";

// Get the image and bind it to the model's input
try
{
  StorageFile file = StorageFile::GetFileFromPathAsync(imagePath).get();
  IRandomAccessStream stream = file.OpenAsync(FileAccessMode::Read).get();
  BitmapDecoder decoder = BitmapDecoder::CreateAsync(stream).get();
  SoftwareBitmap softwareBitmap = decoder.GetSoftwareBitmapAsync().get();
  VideoFrame videoFrame = VideoFrame::CreateWithSoftwareBitmap(softwareBitmap);
  ImageFeatureValue image = ImageFeatureValue::CreateFromVideoFrame(videoFrame);
  binding1.Bind(input.Name(), image);
}
catch (...)
{
  printf("Failed to load/bind image\n");
}
//get the input descriptor
ILearningModelFeatureDescriptor input = model.InputFeatures[0];
//load a SoftwareBitmap
string imagePath = "path\\to\\fish_720.png";

// Get the image and bind it to the model's input
try
{
    StorageFile file = await StorageFile.GetFileFromPathAsync(imagePath);
    IRandomAccessStream stream = await file.OpenAsync(FileAccessMode.Read);
    BitmapDecoder decoder = await BitmapDecoder.CreateAsync(stream);
    SoftwareBitmap softwareBitmap = await decoder.GetSoftwareBitmapAsync();
    VideoFrame videoFrame = VideoFrame.CreateWithSoftwareBitmap(softwareBitmap);
    ImageFeatureValue image = ImageFeatureValue.CreateFromVideoFrame(videoFrame);
    binding1.Bind(input.Name, image);
}
catch
{
    Console.WriteLine("Failed to load/bind image");
}
  1. Damit das nächste Modell in der Kette die Ausgaben der Auswertung des ersten Modells verwendet, muss ein leerer Ausgabetensor erstellt und die Ausgabe gebunden werden, damit eine Markierung für die Verkettung vorhanden ist:
//get the output descriptor
ILearningModelFeatureDescriptor output = model.OutputFeatures().GetAt(0);
//create an empty output tensor 
std::vector<int64_t> shape = {1, 3, 720, 720};
TensorFloat outputValue = TensorFloat::Create(shape); 
//bind the (empty) output
binding1.Bind(output.Name(), outputValue);
//get the output descriptor
ILearningModelFeatureDescriptor output = model.OutputFeatures[0];
//create an empty output tensor 
List<long> shape = new List<long> { 1, 3, 720, 720 };
TensorFloat outputValue = TensorFloat.Create(shape);
//bind the (empty) output
binding1.Bind(output.Name, outputValue);

Hinweis

Beim Binden der Ausgabe müssen Sie den TensorFloat-Datentyp verwenden. Dadurch wird verhindert, dass Detensorisierung durchgeführt wird, sobald die Auswertung für das erste Modell abgeschlossen ist, und somit werden auch zusätzliche GPU-Warteschlangen für Lade- und Bindungsvorgänge für das zweite Modell vermieden.

  1. Nun führen wir die Auswertung des ersten Modells aus und binden seine Ausgaben an die Eingabe des nächsten Modells:
//run session1 evaluation
session1.EvaluateAsync(binding1, L"");
//bind the output to the next model input
binding2.Bind(input.Name(), outputValue);
//run session2 evaluation
auto session2AsyncOp = session2.EvaluateAsync(binding2, L"");
//run session1 evaluation
await session1.EvaluateAsync(binding1, "");
//bind the output to the next model input
binding2.Bind(input.Name, outputValue);
//run session2 evaluation
LearningModelEvaluationResult results = await session2.EvaluateAsync(binding2, "");
  1. Schließlich rufen wir die endgültige Ausgabe ab, die nach dem Ausführen beider Modelle generiert wurde, indem wir die folgende Codezeile verwenden.
auto finalOutput = session2AsyncOp.get().Outputs().First().Current().Value();
var finalOutput = results.Outputs.First().Value;

Das ist alles! Beide Modelle können nun nacheinander ausgeführt werden, indem Sie die verfügbaren GPU-Ressourcen optimal nutzen.

Hinweis

Verwende die folgenden Ressourcen, wenn du Hilfe mit Windows ML benötigst:

  • Wenn du technische Fragen zu Windows ML stellen oder beantworten möchtest, verwende das Tag windows-machine-learning auf Stack Overflow.
  • Wenn du einen Fehler melden möchtest, erstelle eine Anfrage auf GitHub.