コンテンツにスキップ

基本的なC#チュートリアル

C# APIを使用した推論の始め方を学びます。

新しいOrtValueベースのAPIが推奨されるアプローチです。OrtValue APIは、生成されるガベージが少なく、よりパフォーマンスが高くなります。一部のシナリオでは、以前のAPIに比べて4倍のパフォーマンス向上が見られ、ガベージが大幅に削減されます。

OrtValueは、テンソル、マップ、シーケンスなど、さまざまなONNX型を保持できるユニバーサルコンテナです。これは常にonnxruntimeライブラリに存在していましたが、C# APIでは公開されていませんでした。

OrtValueベースのAPIは、マネージドかアンマネージドかに関わらず、その場所に関係なく、ReadOnlySpan<T>およびSpan<T>構造体を介してデータへの統一されたアクセスを提供します。

NamedOnnxValueDisposableNamedOnnxValueFixedBufferOnnxValueなどのクラスは将来廃止される予定であることに注意してください。新しいコードでは推奨されません。

新しいSpanベースのAPIは1次元のインデックスしか持たないため、DenseTensorクラスはデータの多次元アクセスに使用できます。ただし、DenseTensorクラスの多次元アクセスを使用するとパフォーマンスが低下するという報告もあります。その場合、テンソルデータの上にOrtValueを作成できます。

ShapeUtilsクラスは、OrtValuesの多次元インデックスを扱うためのいくつかのヘルプを提供します。

出力形状がわかっている場合は、マネージドまたはアンマネージドのアロケーションの上にOrtValueを事前に割り当て、それらのOrtValueを出力として使用するように指定できます。このため、IOBindingの必要性は大幅に減少します。

OrtValueは、マネージドのunmanaged構造体ベースのblittable型配列の上に直接作成できます。onnxruntime C# APIは、入力または出力にマネージドバッファを使用できます。

文字列データは、C#ではUTF-16文字列オブジェクトとして表されます。ネイティブメモリにコピーしてUTF-8に変換する必要があります。ただし、その変換は現在より最適化されており、中間バイト配列なしで単一パスで実行されます。

同じことが、出力として返される文字列OrtValueテンソルにも当てはまります。文字ベースのAPIは、Span<char>ReadOnlySpan<char>、およびReadOnlyMemory<char>オブジェクトで動作するようになりました。これにより、APIに柔軟性が加わり、不要なコピーを回避できます。

上記の廃止予定のAPIクラスの一部を除き、C# APIクラスのほぼすべてがIDisposableです。 つまり、使用後に破棄する必要があります。そうしないと、メモリリークが発生します。OrtValueはテンソルデータを保持するために使用されるため、リークのサイズは非常に大きくなる可能性があります。各推論呼び出しには入力OrtValueが必要で、出力OrtValueが返されるため、Run呼び出しごとに蓄積される可能性があります。 ファイナライザは実行されることが保証されておらず、実行されても手遅れになるため、期待しないでください。

これには、SessionOptionsRunOptionsInferenceSessionOrtValueが含まれます。Run()呼び出しは、IDisposableCollectionを返します。これにより、含まれるすべてのオブジェクトを1つのステートメントまたはusingで破棄できます。これは、これらのオブジェクトがネイティブリソース(多くの場合ネイティブオブジェクト)を所有しているためです。

マネージドバッファの上に作成されたOrtValueを破棄しないと、 そのバッファがメモリ内で無期限にピン留めされたままになります。このようなバッファは、ガベージコレクションされたり、メモリ内で移動したりすることはできません。

ネイティブのonnxruntimeメモリの上に作成されたOrtValueも、迅速に破棄する必要があります。そうしないと、ネイティブメモリが解放されません。Run()によって返されるOrtValueは通常、ネイティブメモリを保持します。

GCは、ネイティブメモリやその他のネイティブリソースでは動作できません。

usingステートメントまたはブロックは、オブジェクトが確実に破棄されるようにするための便利な方法です。 InferenceSessionは、長寿命のオブジェクトであり、別のクラスのメンバーである可能性があります。最終的には破棄する必要があります。つまり、これを実現するには、含むクラスも破棄可能にする必要があります。

OrtValue APIは、ONNXマップとシーケンスをウォークするためのビジターのようなAPIも提供します。 これは、ONNX Runtimeデータにアクセスするためのより効率的な方法です。

モデルを実行するためのコード例

Section titled “モデルを実行するためのコード例”

モデルを使用してスコアリングを開始するには、InferenceSessionクラスを使用してセッションを作成し、モデルへのファイルパスをパラメータとして渡します。

using var session = new InferenceSession("model.onnx");

セッションが作成されると、InferenceSessionオブジェクトのRunメソッドを使用して推論を実行できます。

float[] sourceData; // データがフラットなfloat配列にロードされていると仮定します
long[] dimensions; // そして入力の次元がここに格納されていると仮定します
// sourceData配列の上にOrtValueを作成します
using var inputOrtValue = OrtValue.CreateTensorValueFromMemory(sourceData, dimensions);
var inputs = new Dictionary<string, OrtValue> {
{ "name1", inputOrtValue }
};
using var runOptions = new RunOptions();
// 入力を渡し、最初の出力を要求します
// 出力はOrtValueを保持する破棄可能なコレクションであることに注意してください
using var output = session.Run(runOptions, inputs, session.OutputNames[0]);
var output_0 = output[0];
// 出力にfloatデータのテンソルが含まれていると仮定して、次のようにアクセスできます
// ネイティブメモリを直接指すSpan<float>を返します。
var outputData = output_0.GetTensorDataAsSpan<float>();
// 出力の詳細情報に関心がある場合は、その型と形状を要求します
// テンソルであると仮定します
// これは破棄可能ではなく、GCされます
// ここでShape、ElementDataTypeなどを要求できます
var tensorTypeAndShape = output_0.GetTensorTypeAndShape();

既存のコードがある場合は、引き続きTensorクラスをデータ操作に使用できます。 次に、Tensorバッファの上にOrtValueを作成します。

// テンソルインターフェイスを使用してデータを作成および操作します
DenseTensor<float> t1 = new DenseTensor<float>(sourceData, dimensions);
// 1つの小さな不便な点は、Tensorクラスが`int`の次元とインデックスで動作することです。
// OrtValueの次元は`long`です。これは、`OrtValue`が直接
// Ort APIと通信し、ライブラリがlong次元を使用するため、必須です。
// dimsを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);
}