Uruchamianie modelu przy użyciu interfejsu API środowiska uruchomieniowego Windows ML

Important

Interfejsy API środowiska uruchomieniowego Windows ML są obecnie eksperymentalne i nie są obsługiwane do użycia w środowiskach produkcyjnych. Aplikacje, które wypróbują te interfejsy API, nie powinny być publikowane w sklepie Microsoft Store.

W tym samouczku przedstawiono standardową sekwencję interfejsu API środowiska uruchomieniowego bez określania sposobu, w jaki aplikacja uzyskuje lub pakuje swój model. Model może znajdować się w dowolnym miejscu, które aplikacja może odczytać; przekaż ścieżkę bezwzględną lub względną do LoadModelFromFile.

Poniższy pomocnik celowo zakłada model z jednym wejściem FLOAT32 o stałym kształcie. Dzięki temu kod alokacji tensora koncentruje się na interfejsie API środowiska uruchomieniowego i nie jest ograniczeniem produktu. Jeśli aplikacja używa swobodnych wymiarów, powiąż konkretne wymiary tensorowe przy każdym uruchomieniu; jeśli używa innych typów danych, utwórz pasujący bufor wejściowy.

Zanim rozpoczniesz

Konfigurowanie projektu języka C++ dla interfejsu API środowiska uruchomieniowego Windows ML. Potrzebujesz również modelu ONNX, którego kontrakt wejściowy rozumiesz.

Dodaj pomocnika do uruchamiania modelu

Dodaj następujące nagłówki i pomocnika RunModel do projektu:

// 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';
}

Przekaż docelowy cel wykonywania do AddModelStage i fabryki tensorów, aby zachować przygotowanie modelu i umieszczanie tensorów na tym samym obiekcie docelowym.

Lock ustanawia okno dostępu procesora CPU dla tensora danych wyjściowych. Wykonuje ono dowolne wymagane mapowanie lub synchronizację, a wskaźnik zwracany przez GetData pozostaje prawidłowy tylko wtedy, gdy outputLock istnieje.

Wywołaj pomocnika

Wywołaj metodę RunModel podając ścieżkę modelu. Na przykład następujące wmain akceptuje ścieżkę w wierszu polecenia:

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;
}

Skompiluj i uruchom aplikację:

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

Zastąp ścieżkę ścieżką do swojego modelu. Zinterpretuj zwrócony tensor zgodnie ze schematem danych wyjściowych tego modelu; etykiety klasyfikacji, próbkowanie tokenów, wstępne przetwarzanie obrazów i inne zachowanie specyficzne dla modelu należą do Twojej aplikacji.

Następne kroki