C# 教程:基礎
瞭解如何使用 C# API 進行推理。
OrtValue API
推薦使用新的基於 OrtValue 的 API。 OrtValue API 產生的垃圾更少,效能更高。某些場景顯示效能比以前的 API 提高了 4 倍,並且垃圾顯著減少。
OrtValue 是一個通用容器,可以容納不同的 ONNX 型別,例如張量、對映和序列。它一直存在於 onnxruntime 庫中,但並未在 C# API 中公開。
基於 OrtValue 的 API 透過 ReadOnlySpan<T> 和 Span<T> 結構提供統一的資料訪問,無論資料位於託管記憶體還是非託管記憶體中。
請注意,NamedOnnxValue、DisposableNamedOnnxValue、FixedBufferOnnxValue 等類將來將被棄用。新程式碼不推薦使用它們。
資料形狀
由於新的基於 Span 的 API 只支援一維索引,因此可以使用 DenseTensor 類進行多維資料訪問。然而,有人報告說使用 DenseTensor 類的多維訪問速度較慢。在這種情況下,可以在張量資料之上建立 OrtValue。
ShapeUtils 類提供了一些幫助,用於處理 OrtValues 的多維索引。
如果輸出形狀已知,可以在託管或非託管分配之上預先分配 OrtValue,並提供這些 OrtValues 作為輸出。因此,對 IOBinding 的需求大大減少。
資料型別
OrtValues 可以直接在託管的unmanaged 基於 struct 的可複製型別陣列之上建立。onnxruntime C# API 允許使用託管緩衝區進行輸入或輸出。
字串資料在 C# 中表示為 UTF-16 字串物件。它仍然需要複製並轉換為 UTF-8 到本地記憶體。然而,現在這種轉換經過了更多最佳化,並且在沒有中間位元組陣列的情況下一次性完成。
同樣適用於作為輸出返回的字串 OrtValue 張量。基於字元的 API 現在操作 Span<char>、ReadOnlySpan<char> 和 ReadOnlyMemory<char> 物件。這增加了 API 的靈活性,並允許避免不必要的複製。
資料生命週期
除了上面提到的一些已棄用的 API 類之外,幾乎所有 C# API 類都實現了 IDisposable 介面。這意味著它們在使用後需要被釋放,否則會導致記憶體洩漏。由於 OrtValues 用於儲存張量資料,洩漏的大小可能非常巨大。每次 Run 呼叫都可能導致洩漏累積,因為每次推理呼叫都需要輸入 OrtValues 並返回輸出 OrtValues。不要指望終結器,它們不保證一定會執行,即使執行也為時已晚。
這包括 SessionOptions, RunOptions, InferenceSession, OrtValue。 Run() 呼叫返回 IDisposableCollection,允許透過一個語句或 using 關鍵字來釋放所有包含的物件。這是因為這些物件擁有本地資源,通常是本地物件。
不釋放基於託管緩衝區建立的 OrtValue 將導致該緩衝區無限期地固定在記憶體中。這樣的緩衝區無法被垃圾回收或在記憶體中移動。
在本地 onnxruntime 記憶體上建立的 OrtValue 也應及時釋放。否則,本地記憶體將不會被釋放。Run() 返回的 OrtValues 通常持有本地記憶體。
垃圾回收器不能操作本地記憶體或任何其他本地資源。
using 語句或程式碼塊是確保物件被釋放的便捷方法。InferenceSession 可以是一個生命週期較長的物件,並且是另一個類的成員。它最終必須被釋放。這意味著,包含它的類也必須實現 IDisposable 才能實現這一點。
OrtValue API 還提供了類似訪問者(visitor)的 API 來遍歷 ONNX 對映和序列。這是一種更有效率的訪問 ONNX Runtime 資料的方式。
執行模型的程式碼示例
要開始使用模型進行評分,請使用 InferenceSession 類建立一個會話,並將模型的 檔案路徑 作為引數傳入。
using var session = new InferenceSession("model.onnx");
建立會話後,您可以使用 InferenceSession 物件的 Run 方法執行推理。
float[] sourceData; // assume your data is loaded into a flat float array
long[] dimensions; // and the dimensions of the input is stored here
// Create a OrtValue on top of the sourceData array
using var inputOrtValue = OrtValue.CreateTensorValueFromMemory(sourceData, dimensions);
var inputs = new Dictionary<string, OrtValue> {
{ "name1", inputOrtValue }
};
using var runOptions = new RunOptions();
// Pass inputs and request the first output
// Note that the output is a disposable collection that holds OrtValues
using var output = session.Run(runOptions, inputs, session.OutputNames[0]);
var output_0 = output[0];
// Assuming the output contains a tensor of float data, you can access it as follows
// Returns Span<float> which points directly to native memory.
var outputData = output_0.GetTensorDataAsSpan<float>();
// If you are interested in more information about output, request its type and shape
// Assuming it is a tensor
// This is not disposable, will be GCed
// There you can request Shape, ElementDataType, etc
var tensorTypeAndShape = output_0.GetTensorTypeAndShape();
如果您有現有程式碼處理資料操作,仍然可以使用 Tensor 類。然後可以在 Tensor 緩衝區之上建立 OrtValue。
// Create and manipulate the data using tensor interface
DenseTensor<float> t1 = new DenseTensor<float>(sourceData, dimensions);
// One minor inconvenience is that Tensor class operates on `int` dimensions and indices.
// OrtValue dimensions are `long`. This is required, because `OrtValue` talks directly to
// Ort API and the library uses long dimensions.
// Convert dims to long[]
var shape = Array.Convert<int,long>(dimensions, Convert.ToInt64);
using var inputOrtValue = OrtValue.CreateTensorValueFromMemory(OrtMemoryInfo.DefaultInstance,
t1.Buffer, shape);
以下是填充字串張量的方法。字串不能直接對映,必須複製/轉換為本地記憶體。為此,我們預先分配一個指定維度的空字串本地張量,然後透過索引設定單個字串。
string[] strs = { "Hello", "Ort", "World" };
long[] shape = { 1, 1, 3 };
var elementsNum = ShapeUtils.GetSizeForShape(shape);
using var strTensor = OrtValue.CreateTensorWithEmptyStrings(OrtAllocator.DefaultInstance, shape);
for (long i = 0; i < elementsNum; ++i)
{
strTensor.StringTensorSetElementAt(strs[i].AsSpan(), i);
}