Exécution de plusieurs modèles ML dans une chaîne

Windows ML assure la charge et l’exécution hautes performances des chaînes de modèles en optimisant soigneusement son chemin de GPU. Les chaînes de modèles sont définies par deux ou plusieurs modèles qui s’exécutent de manière séquentielle, les sorties d’un modèle devenant les entrées du modèle suivant dans la chaîne.

Afin d’expliquer comment chaîner efficacement des modèles avec Windows ML, nous allons utiliser un modèle ONNX de transfert de style FNS-Candy comme exemple de jouet. Ce type de modèle est disponible dans le dossier de l’exemple de transfert de style FNS-Candy dans notre dépôt GitHub.

Supposons que nous voulons exécuter une chaîne composée de deux instances du même modèle FNS-Candy (en l’occurrence, mosaic.onnx). Le code d’application passe une image au premier modèle de la chaîne, le laisse calculer les sorties, puis passe l’image ainsi transformée à une autre instance de FNS-Candy, produisant une image finale.

Les étapes suivantes montrent comment effectuer cette procédure à l’aide de Windows ML.

Remarque

Dans un scénario concret, vous utiliseriez probablement deux modèles différents, mais cela devrait suffire pour illustrer les concepts.

  1. Commençons par charger le modèle mosaic.onnx pour pouvoir l’utiliser.
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. Créons ensuite deux sessions identiques sur le GPU par défaut de l’appareil en utilisant le même modèle comme paramètre d’entrée.
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));

Remarque

Afin d’exploiter les avantages que procure le chaînage en termes de performances, vous devez créer des sessions GPU identiques pour tous vos modèles. Si vous ne le faites pas, les performances s’en ressentiront, en raison d’un déplacement de données supplémentaire hors du GPU et vers le processeur.

  1. Les lignes de code suivantes créent des liaisons pour chaque session :
LearningModelBinding binding1(session1);
LearningModelBinding binding2(session2);
LearningModelBinding binding1 = new LearningModelBinding(session1);
LearningModelBinding binding2 = new LearningModelBinding(session2);
  1. Nous allons à présent lier une entrée pour notre premier modèle. Nous allons transmettre une image qui se trouve dans le même chemin que notre modèle. Dans cet exemple, l’image est appelée « 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. Pour que le modèle suivant dans la chaîne utilise les sorties de l’évaluation du premier modèle, nous devons créer un tenseur de sortie vide et lier la sortie afin de disposer d’un marqueur avec lequel effectuer le chaînage :
//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);

Remarque

Vous devez utiliser le type de données TensorFloat lors de la liaison de la sortie. Ainsi, aucune détenseurisation ne se produit une fois que l’évaluation pour le premier modèle est terminée, ce qui évite également une mise en file d’attente GPU supplémentaire pour les opérations de chargement et de liaison liées au deuxième modèle.

  1. À présent, nous exécutons l’évaluation du premier modèle et nous lions ses sorties à l’entrée du modèle suivant :
//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. Enfin, nous allons récupérer la sortie finale produite après l’exécution des deux modèles à l’aide de la ligne de code suivante.
auto finalOutput = session2AsyncOp.get().Outputs().First().Current().Value();
var finalOutput = results.Outputs.First().Value;

Et voilà ! Vos deux modèles peuvent désormais s’exécuter de manière séquentielle en tirant parti des ressources GPU disponibles.

Remarque

Utilisez les ressources suivantes pour obtenir de l’aide sur Windows ML :

  • Pour poser des questions techniques ou apporter des réponses à des questions techniques sur Windows ML, veuillez utiliser le mot clé windows-machine-learning sur Stack Overflow.
  • Pour signaler un bogue, veuillez signaler un problème dans notre plateforme GitHub.