Ejecución de un modelo con la API de tiempo de ejecución de ML de Windows

Importante

Las API de tiempo de ejecución de ML de Windows son actualmente experimentales y no se admiten para su uso en entornos de producción. Las aplicaciones que prueban estas API no deben publicarse en Microsoft Store.

En este tutorial se muestra la secuencia estándar de la API en tiempo de ejecución sin especificar cómo la aplicación adquiere o empaqueta su modelo. El modelo puede estar en cualquier ubicación donde la aplicación pueda leer; pase una ruta de acceso absoluta o relativa a LoadModelFromFile.

El asistente siguiente asume intencionadamente un modelo con una entrada FLOAT32 de forma fija. Esto mantiene el código de asignación de tensor centrado en la API en tiempo de ejecución y no es una limitación del producto. Si la aplicación usa dimensiones libres, vincule dimensiones tensor concretas para cada ejecución; si usa otros tipos de datos, cree un almacenamiento de entrada coincidente.

Antes de empezar

Completa Configurar un proyecto de C++ para la API de Windows ML Runtime. También necesitas un modelo ONNX cuyo contrato de entrada comprendas.

Añadir un asistente para ejecutar modelos

Agregue estos encabezados y el RunModel asistente al proyecto:

// Copyright (C) Microsoft Corporation. All rights reserved.

#include <Windows.h>
#include <cstdint>
#include <iostream>
#include <string>
#include <vector>
#include <winrt/base.h>

#include <WinMLRuntime.h>

#pragma comment(lib, "windowsapp.lib")

void RunModel(std::wstring const& modelPath)
{
    winrt::com_ptr<IWinMLRuntime> runtime;
    winrt::check_hresult(WinMLCreateRuntime(
        __uuidof(IWinMLRuntime),
        runtime.put_void()));

    winrt::com_ptr<IWinMLModel> model;
    winrt::check_hresult(runtime->LoadModelFromFile(
        modelPath.c_str(),
        nullptr,
        nullptr,
        model.put()));

    winrt::com_ptr<IWinMLExecutionTarget> target;
    winrt::check_hresult(runtime->CreateCpuExecutionTarget(target.put()));

    winrt::com_ptr<IWinMLPipelineBuilder> builder;
    winrt::check_hresult(runtime->CreatePipelineBuilder(builder.put()));

    winrt::com_ptr<IWinMLStage> stage;
    winrt::check_hresult(builder->AddModelStage(
        model.get(),
        target.get(),
        L"model",
        stage.put()));

    winrt::com_ptr<IWinMLPipeline> pipeline;
    winrt::check_hresult(builder->Build(pipeline.put()));

    winrt::com_ptr<IWinMLModelSchema> schema;
    winrt::check_hresult(model->QueryInterface(
        __uuidof(IWinMLModelSchema),
        schema.put_void()));

    WINML_TENSOR_SCHEMA_DESC inputSchema = {};
    winrt::check_hresult(schema->GetInputTensorDesc(0, &inputSchema));

    if (inputSchema.dataType != WINML_TENSOR_DATA_TYPE_FLOAT32 ||
        (inputSchema.dimensionCount > 0 &&
         inputSchema.dimensions == nullptr))
    {
        winrt::throw_hresult(E_INVALIDARG);
    }

    UINT64 elementCount = 1;
    for (UINT32 i = 0; i < inputSchema.dimensionCount; ++i)
    {
        const UINT64 dimension = inputSchema.dimensions[i];
        if (dimension == 0 ||
            dimension == UINT64_MAX ||
            elementCount > UINT64_MAX / dimension)
        {
            winrt::throw_hresult(E_INVALIDARG);
        }

        elementCount *= dimension;
    }

    if (elementCount > SIZE_MAX)
    {
        winrt::throw_hresult(E_BOUNDS);
    }

    std::vector<float> inputData(
        static_cast<size_t>(elementCount),
        0.5f);

    WINML_TENSOR_DESC inputDesc = {};
    inputDesc.dataType = inputSchema.dataType;
    inputDesc.dimensionCount = inputSchema.dimensionCount;
    inputDesc.dimensions = inputSchema.dimensions;

    winrt::com_ptr<IWinMLRawTensorFactory> tensorFactory;
    winrt::check_hresult(target->QueryInterface(
        __uuidof(IWinMLRawTensorFactory),
        tensorFactory.put_void()));

    winrt::com_ptr<IWinMLTensor> inputTensor;
    winrt::check_hresult(tensorFactory->CreateTensor(
        &inputDesc,
        inputData.data(),
        inputData.size() * sizeof(float),
        inputTensor.put()));

    winrt::check_hresult(stage->BindInput(0, inputTensor.get()));
    winrt::check_hresult(pipeline->Run());

    winrt::com_ptr<IWinMLTensor> outputTensor;
    winrt::check_hresult(stage->GetOutput(0, outputTensor.put()));

    winrt::com_ptr<IWinMLTensorDataLock> outputLock;
    winrt::check_hresult(outputTensor->Lock(
        WINML_TENSOR_LOCK_MODE_READ,
        WINML_TENSOR_LOCK_FLAG_NONE,
        outputLock.put()));

    BYTE* outputBytes = nullptr;
    UINT64 outputSizeInBytes = 0;
    winrt::check_hresult(outputLock->GetData(
        &outputBytes,
        &outputSizeInBytes));

    (void)outputBytes;
    std::wcout << L"Output bytes: " << outputSizeInBytes << L'\n';
}

Pasa el destino de ejecución tanto a AddModelStage como a la fábrica de tensores para mantener la preparación del modelo y la colocación de los tensores en el mismo destino.

Lock establece una ventana de acceso a la CPU para el tensor de salida. Realiza cualquier mapeo o sincronización necesaria y el puntero devuelto por GetData permanece válido solo mientras outputLock está activo.

Llama a la función auxiliar

Llame a RunModel con la ruta de acceso del modelo. Por ejemplo, el siguiente comando wmain acepta la ruta de acceso en la línea de comandos:

int wmain(int argc, wchar_t** argv)
{
    if (argc != 2)
    {
        std::wcerr << L"Usage: RuntimeApp.exe <model-path>\n";
        return 1;
    }

    RunModel(argv[1]);
    return 0;
}

Compile y ejecute la aplicación:

.\x64\Release\RuntimeApp.exe C:\models\model.onnx

Sustituye la ruta por la de tu modelo. Interpreta el tensor devuelto según el esquema de salida de ese modelo; las etiquetas de clasificación, el muestreo de tokens, el preprocesamiento de imágenes y otros comportamientos específicos del modelo deben formar parte de tu aplicación.

Pasos siguientes