Erste Schritte mit ORT für C#

Inhalt

Installieren Sie die Nuget-Pakete mit der .NET CLI

dotnet add package Microsoft.ML.OnnxRuntime --version 1.16.0
dotnet add package System.Numerics.Tensors --version 0.1.0

Importieren Sie die Bibliotheken

using Microsoft.ML.OnnxRuntime;
using System.Numerics.Tensors;

Methode zur Inferenz erstellen

Dies ist ein Beispiel für eine Azure Function, die ORT mit C# zur Inferenz eines mit SciKit Learn erstellten NLP-Modells verwendet.

 public static async Task<IActionResult> Run(
            [HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = null)] HttpRequest req,
            ILogger log, ExecutionContext context)
        {
            log.LogInformation("C# HTTP trigger function processed a request.");

            string review = req.Query["review"];

            string requestBody = await new StreamReader(req.Body).ReadToEndAsync();
            dynamic data = JsonConvert.DeserializeObject(requestBody);
            review ??= data.review;
            Debug.Assert(!string.IsNullOrEmpty(review), "Expecting a string with a content");

            // Get path to model to create inference session.
            const string modelPath = "./model.onnx";

            // Create an InferenceSession from the Model Path.
            // Creating and loading sessions are expensive per request.
            // They better be cached
            using var session = new InferenceSession(modelPath);

            // create input tensor (nlp example)
            using var inputOrtValue = OrtValue.CreateTensorWithEmptyStrings(OrtAllocator.DefaultInstance, new long[] { 1, 1 });
            inputOrtValue.StringTensorSetElementAt(review, 0);

            // Create input data for session. Request all outputs in this case.
            var inputs = new Dictionary<string, OrtValue>
            {
                { "input", inputOrtValue }
            };

            using var runOptions = new RunOptions();

            // We are getting a sequence of maps as output. We are interested in the first element (map) of the sequence.
            // That result is a Sequence of Maps, and we only need the first map from there.
            using var outputs = session.Run(runOptions, inputs, session.OutputNames);
            Debug.Assert(outputs.Count > 0, "Expecting some output");

            // We want the last output, which is the sequence of maps
            var lastOutput = outputs[outputs.Count - 1];

            // Optional code to check the output type
            {
                var outputTypeInfo = lastOutput.GetTypeInfo();
                Debug.Assert(outputTypeInfo.OnnxType == OnnxValueType.ONNX_TYPE_SEQUENCE, "Expecting a sequence");

                var sequenceTypeInfo = outputTypeInfo.SequenceTypeInfo;
                Debug.Assert(sequenceTypeInfo.ElementType.OnnxType == OnnxValueType.ONNX_TYPE_MAP, "Expecting a sequence of maps");
            }

            var elementsNum = lastOutput.GetValueCount();
            Debug.Assert(elementsNum > 0, "Expecting a non empty sequence");

            // Get the first map in sequence
            using var firstMap = lastOutput.GetValue(0, OrtAllocator.DefaultInstance);

            // Optional code just checking
            {
                // Maps always have two elements, keys and values
                // We are expecting this to be a map of strings to floats
                var mapTypeInfo = firstMap.GetTypeInfo().MapTypeInfo;
                Debug.Assert(mapTypeInfo.KeyType == TensorElementType.String, "Expecting keys to be strings");
                Debug.Assert(mapTypeInfo.ValueType.OnnxType == OnnxValueType.ONNX_TYPE_TENSOR, "Values are in the tensor");
                Debug.Assert(mapTypeInfo.ValueType.TensorTypeAndShapeInfo.ElementDataType == TensorElementType.Float, "Result map value is float");
            }

            var inferenceResult = new Dictionary<string, float>();
            // Let use the visitor to read map keys and values
            // Here keys and values are represented with the same number of corresponding entries
            // string -> float
            firstMap.ProcessMap((keys, values) => {
                // Access native buffer directly
                var valuesSpan = values.GetTensorDataAsSpan<float>();

                var entryCount = (int)keys.GetTensorTypeAndShape().ElementCount;
                inferenceResult.EnsureCapacity(entryCount);
                for (int i = 0; i < entryCount; ++i)
                {
                    inferenceResult.Add(keys.GetStringElement(i), valuesSpan[i]);
                }
            }, OrtAllocator.DefaultInstance);


            // Return the inference result as json.
            return new JsonResult(inferenceResult);

        }

Eingabe-/Ausgabe-Tensorpuffer wiederverwenden

In einigen Szenarien möchten Sie möglicherweise Eingabe-/Ausgabe-Tensoren wiederverwenden. Dies geschieht häufig, wenn Sie 2 Modelle verketten möchten (d. h. die Ausgabe eines als Eingabe für ein anderes verwenden) oder die Inferenzgeschwindigkeit bei mehreren Inferenzläufen beschleunigen möchten.

Verketten: Ausgabe(n) von Modell A als Eingabe(n) für Modell B übergeben

using Microsoft.ML.OnnxRuntime.Tensors;
using Microsoft.ML.OnnxRuntime;

namespace Samples
{
    class FeedModelAToModelB
    {
        static void Program()
        {
            const string modelAPath = "./modelA.onnx";
            const string modelBPath = "./modelB.onnx";
            using InferenceSession session1 = new InferenceSession(modelAPath);
            using InferenceSession session2 = new InferenceSession(modelBPath);

            // Illustration only
            float[] inputData = { 1, 2, 3, 4 };
            long[] inputShape = { 1, 4 };

            using var inputOrtValue = OrtValue.CreateTensorValueFromMemory(inputData, inputShape);

            // Create input data for session. Request all outputs in this case.
            var inputs1 = new Dictionary<string, OrtValue>
            {
                { "input", inputOrtValue }
            };

            using var runOptions = new RunOptions();

            // session1 inference
            using (var outputs1 = session1.Run(runOptions, inputs1, session1.OutputNames))
            {
                // get intermediate value
                var outputToFeed = outputs1.First();

                // modify the name of the ONNX value
                // create input list for session2
                var inputs2 = new Dictionary<string, OrtValue>
                {
                    { "inputNameForModelB", outputToFeed }
                };

                // session2 inference
                using (var results = session2.Run(runOptions, inputs2, session2.OutputNames))
                {
                    // manipulate the results
                }
            }
        }
    }
}

Mehrere Inferenzläufe mit festen Ein- und Ausgängen

Wenn das Modell feste Eingaben und Ausgaben von numerischen Tensoren hat, verwenden Sie den bevorzugten OrtValue und seine API, um die Inferenzgeschwindigkeit zu erhöhen und die Datenübertragung zu minimieren. Die Klasse OrtValue ermöglicht die Wiederverwendung des zugrunde liegenden Puffers für die Ein- und Ausgabetensoren. Sie heftet verwaltete Puffer an und nutzt diese für die Inferenz. Sie bietet auch direkten Zugriff auf die nativen Puffer für Ausgaben. Sie können auch OrtValue für Ausgaben vorab zuweisen oder ihn auf vorhandenen Puffern erstellen. Dies vermeidet einige Overhead, was für kleinere Modelle von Vorteil sein kann, bei denen die Zeit in der gesamten Laufzeit spürbar ist.

Beachten Sie, dass die Klasse OrtValue, wie viele andere Klassen in der Onnruntime C#-API, IDisposable ist. Sie muss ordnungsgemäß entsorgt werden, um entweder die verwalteten Puffer zu lösen oder die nativen Puffer freizugeben, um Speicherlecks zu vermeiden.

Ausführung auf GPU (optional)

Wenn Sie das GPU-Paket verwenden, verwenden Sie einfach die entsprechenden SessionOptions beim Erstellen einer InferenceSession.

int gpuDeviceId = 0; // The GPU device ID to execute on
using var gpuSessionOptoins = SessionOptions.MakeSessionOptionWithCudaProvider(gpuDeviceId);
using var session = new InferenceSession("model.onnx", gpuSessionOptoins);

ONNX Runtime C#-API

Die ONNX Runtime bietet eine C#- .NET-Bindung für die Ausführung von Inferenz auf ONNX-Modellen auf allen .NET-Standardplattformen.

Unterstützte Versionen

.NET Standard 1.1

Builds

Artefakt Beschreibung Unterstützte Plattformen
Microsoft.ML.OnnxRuntime CPU (Release) Windows, Linux, Mac, X64, X86 (nur Windows), ARM64 (nur Windows)... mehr Details: Kompatibilität
Microsoft.ML.OnnxRuntime.Gpu GPU - CUDA (Release) Windows, Linux, Mac, X64... mehr Details: Kompatibilität
Microsoft.ML.OnnxRuntime.DirectML GPU - DirectML (Release) Windows 10 1709+
onnxruntime CPU, GPU (Dev), CPU (On-Device Training) Dasselbe wie bei Release-Versionen
Microsoft.ML.OnnxRuntime.Training CPU On-Device Training (Release) Windows, Linux, Mac, X64, X86 (nur Windows), ARM64 (nur Windows)... mehr Details: Kompatibilität

API-Referenz

C#-API-Referenz

Beispiele

Siehe Tutorials: Grundlagen - C#

Mehr erfahren