Erste Schritte mit ORT für C#
Inhalt
- Installieren Sie die Nuget-Pakete mit der .NET CLI
- Importieren Sie die Bibliotheken
- Methode zur Inferenz erstellen
- Eingabe-/Ausgabe-Tensorpuffer wiederverwenden
- Ausführung auf GPU (optional)
- Unterstützte Versionen
- Builds
- API-Referenz
- Beispiele
- Mehr erfahren
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
Beispiele
Siehe Tutorials: Grundlagen - C#