Ejecución de varios modelos de ML en una cadena

Windows ML admite la carga y ejecución de alto rendimiento de cadenas de modelos mediante la optimización minuciosa de la ruta de acceso de la GPU. Las cadenas de modelos se definen mediante dos o más modelos que se ejecutan secuencialmente, donde las salidas de un modelo se convierten en las entradas del siguiente en la cadena.

Para explicar cómo encadenar modelos con Windows ML de forma eficaz, vamos a usar el modelo de transferencia de estilo FNS-Candy de ONNX como ejemplo. Puedes encontrar este tipo de modelo en la carpeta de ejemplo FNS-Candy Style Transfer en GitHub.

Supongamos que queremos ejecutar una cadena que se compone de dos instancias del mismo modelo FNS-Candy, que aquí se denomina mosaic.onnx. El código de la aplicación pasaría una imagen al primer modelo de la cadena, permitiría calcular las salidas y, a continuación, pasaría esa imagen transformada a otra instancia de FNS-Candy, lo que generaría una imagen final.

En los pasos siguientes se muestra cómo lograrlo con Windows ML.

Nota:

En un escenario real, lo más probable es que utilices dos modelos diferentes, pero este debería ser suficiente para ilustrar los conceptos.

  1. En primer lugar, vamos a cargar el modelo mosaic.onnx para poder usarlo.
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. A continuación, vamos a crear dos sesiones idénticas en la GPU predeterminada del dispositivo con el mismo modelo que el 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));

Nota:

Para aprovechar las ventajas de rendimiento del encadenamiento, debes crear sesiones de GPU idénticas para todos los modelos. Si no lo haces, los datos adicionales se trasladarán de la GPU a la CPU, lo que reducirá el rendimiento.

  1. Las siguientes líneas de código crearán enlaces para cada sesión:
LearningModelBinding binding1(session1);
LearningModelBinding binding2(session2);
LearningModelBinding binding1 = new LearningModelBinding(session1);
LearningModelBinding binding2 = new LearningModelBinding(session2);
  1. A continuación, enlazaremos una entrada para nuestro primer modelo. Pasaremos una imagen que se encuentra en la misma ruta de acceso que nuestro modelo. En este ejemplo, la imagen se denomina "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 el siguiente modelo de la cadena use las salidas de la evaluación del primero, debemos crear un tensor de salida vacío y enlazar la salida para tener un marcador con el que encadenarlo:
//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);

Nota:

Debes usar el tipo de datos TensorFloat al enlazar la salida. Esto impedirá que se anule la tensorización una vez completada la evaluación del primer modelo, lo que también evitará la puesta en cola de la GPU adicional para las operaciones de carga y enlace del segundo modelo.

  1. Ahora, ejecutamos la evaluación del primer modelo y enlazamos sus salidas a la entrada del siguiente:
//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 último, vamos a recuperar la salida final generada después de ejecutar ambos modelos mediante la siguiente línea de código.
auto finalOutput = session2AsyncOp.get().Outputs().First().Current().Value();
var finalOutput = results.Outputs.First().Value;

Eso es todo. Ahora ambos modelos se pueden ejecutar de forma secuencial al aprovechar al máximo los recursos de la GPU disponibles.

Nota:

Use los siguientes recursos para obtener ayuda con Windows ML:

  • Para formular o responder a preguntas técnicas sobre Windows Machine Learning, utilice la etiqueta windows-machine-learning en Stack Overflow.
  • Para notificar un error, registre un problema en GitHub.