Executar vários modelos de ML em uma cadeia

O Windows ML dá suporte a carga de alto desempenho e a execução de cadeias de modelo, otimizando cuidadosamente o próprio caminho de GPU. As cadeias de modelo são definidas por dois ou mais modelos que são executados em sequência, em que as saídas de um modelo se tornam as entradas para o próximo modelo na cadeia.

Para explicar como encadear modelos com eficiência usando o Windows ML, vamos usar um modelo de ONNX de transferência de estilo FNS-Candy como um exemplo descompromissado. Você pode encontrar esse tipo de modelo na pasta de exemplo Transferência de Estilo FNS-Candy em nosso GitHub.

Digamos que desejamos executar uma cadeia composta de duas instâncias do mesmo modelo FNS-Candy, aqui chamado mosaic.onnx. O código do aplicativo passaria uma imagem para o primeiro modelo na cadeia, deixaria que ela calculasse as saídas e, em seguida, passaria essa imagem transformada para outra instância de FNS, produzindo uma imagem final.

As etapas a seguir ilustram como fazer isso usando o Windows ML.

Observação

Em um cenário real, você provavelmente usaria dois modelos diferentes, mas o que estamos fazendo deve ser suficiente para ilustrar os conceitos.

  1. Primeiro, vamos carregar o modelo de mosaic.onnx para que possamos usá-lo.
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. Em seguida, vamos criar duas sessões idênticas na GPU padrão do dispositivo usando o mesmo modelo que o parâmetro de entrada.
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));

Observação

Para colher os benefícios de desempenho do encadeamento, você precisa criar sessões de GPU idênticas para todos os seus modelos. Não fazer isso resultaria em uma movimentação de dados adicional da GPU para a CPU, o que reduziria o desempenho.

  1. As linhas de código a seguir criarão associações para cada sessão:
LearningModelBinding binding1(session1);
LearningModelBinding binding2(session2);
LearningModelBinding binding1 = new LearningModelBinding(session1);
LearningModelBinding binding2 = new LearningModelBinding(session2);
  1. Em seguida, associaremos uma entrada ao nosso primeiro modelo. Passaremos uma imagem que está localizada no mesmo caminho que o nosso modelo. Neste exemplo, a imagem é chamada "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. Para que o próximo modelo na cadeia use as saídas da avaliação do primeiro modelo, precisamos criar um tensor de saída vazio e associar a saída para que tenhamos um marcador com o qual encadear:
//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);

Observação

Você precisa usar o tipo de dados TensorFloat ao associar a saída. Isso impedirá a ocorrência de tensorização após a conclusão da avaliação para o primeiro modelo, evitando também o enfileiramento de GPU adicional para operações de carregamento e associação ao segundo modelo.

  1. Agora, executamos a avaliação do primeiro modelo e associamos as saídas dele à entrada do próximo modelo:
//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. Por fim, vamos recuperar a saída final produzida depois de executar os dois modelos usando a linha de código a seguir.
auto finalOutput = session2AsyncOp.get().Outputs().First().Current().Value();
var finalOutput = results.Outputs.First().Value;

É isso! Ambos os modelos agora podem ser executados em sequência, aproveitando ao máximo os recursos de GPU disponíveis.

Observação

Use os recursos a seguir para obter ajuda com o Windows ML:

  • Para fazer perguntas ou responder a perguntas técnicas sobre o Windows ML, use a marca windows-machine-learning no Stack Overflow.
  • Para relatar um bug, registre um problema no nosso GitHub.