カスタムノード開発ガイド

概要

OpenVINO モデルサーバーのカスタムノードを使用すると、シーケンシャル・モデルの入力と出力が適合しない場合でも、ディープラーニング・モデルを完全なパイプラインにリンクすることができます。多くの場合、あるモデルの出力を別のモデルに直接渡すことはできません。データの分析、フィルター処理、または別の形式への変換が必要になります。この操作は、AI フレームワークでは簡単に実装できないか、サポートされていません。カスタムノードはこの課題に対処します。C++ または C で開発された動的ライブラリーを使用して、任意のデータ変換を実行できます。

カスタムノード API

カスタム・ノード・ライブラリーは、custom_node_interface.h で定義された API インターフェイスを実装する必要があります。インターフェイスは、さまざまなコンパイラーとの互換性を簡素化するため C で定義されています。ライブラリーは、静的または動的にリンクされたサードパーティーのコンポーネントを使用できます。OpenCV は、画像データの操作に使用できる OVMS のビルトイン・コンポーネントです。

API ヘッダーで定義されるデータ構造と関数について説明します。

“CustomNodeTensor” 構造体

CustomNodeTensor 構造体は、ノード実行の出力および入力のデータを定義するフィールドで構成されます。カスタムノードは、1 つ以上の他のノードからの複数入力に基づいて結果を生成できます。“Execute” 関数は、処理される複数の入力を格納する複数の CustomNodeTensor オブジェクトへのポインターにアクセスできます。各入力はインデックスを使用して参照することも、名前で検索することもできます。

inputTensor0 = &(inputs[0])

すべての CustomNodeTensor 構造体には次のフィールドが含まれます。

  • const char* name - 入力名を表す文字列へのポインター。

  • uint8_t* data - データバッファーへのポインター。データはバイト単位で保存されます。

  • uint64_t dataBytes - データ割り当てサイズ (バイト単位)。

  • uint64_t* dims - 配列の形状サイズを格納するバッファーへのポインター。各次元のサイズは 8 バイトです。

  • uint64_t dimsCount - データ配列の次元数。

  • CustomNodeTensorPrecision precision - データ精度の列挙。

CustomNodeTensorInfo 構造体

struct CustomNodeTensorInfo のフィールドは CustomNodeTensor に似ています。これは、カスタム・ノード・インターフェイスのメタデータ (入力と出力) 情報を保持します。

“CustomNodeParam” 構造体

Struct CustomNodeParam は、パラメーターと値の文字列へのポインターを含むペアのリストを保存します。このオブジェクトの各パラメーターは、構造を反復することでキー名で検索できるインデックスを使用して参照できます。

“execute” 関数

int execute(const struct CustomNodeTensor* inputs, int inputsCount, struct CustomNodeTensor** outputs, int* outputsCount, const struct CustomNodeParam* params, int paramsCount);

この関数はカスタムノードのデータ変換を実装します。関数の入力データは、CustomNodeTensor 構造体オブジェクトへのポインター形式で渡されます。これには、すべてのカスタムノード入力のデータとバッファーへのポインターが含まれます。パラメーター inputsCount は、この方法で渡された入力の数に関する情報を渡します。

execute 関数では、入力データを保存するバッファーを変更しないでください。変更すると、他のパイプライン・ノードで使用されるデータが変更される可能性があるためです。

カスタムノード execute 関数の動作は、OVMS 構成で設定されたノード・パラメーターに依存する場合があります。これらは、params 引数で execute 関数に渡されます。paramsCount は、設定されたパラメーター数の情報を渡します。

データ変換の結果は、ustomNodeTensor 構造体のアドレスを格納するポインターへの出力ポインターによって返される必要があります。出力の数は、関数の実行中に、outputsCount 引数で定義されます。

関数の実行中は、すべての出力データバッファーを割り当てる必要があることに注意してください。これらは、要求の処理が完了し、ユーザーに返された後に OVMS によって解放されます。クリーンアップは、release 関数呼び出しでトリガーされます。この関数もカスタム・ライブラリーに実装する必要があります。

execute 呼び出しによる動的割り当てがパフォーマンスのボトルネックになったり、メモリーの断片化を引き起こしたりすることがあります。2022.1 リリース以降、DAG の初期化中にメモリーを事前に割り当て、後続の推論要求で再利用できるようになりました。以下の initializedeinitialize 関数を参照してください。これらは、事前割り当てメモリープールを実装するために使用できます。実装例は、カスタムノードのサンプルソースで確認できます。

Execute 関数は、成功 (0 値) または失敗 (0 以外) を定義する整数値を返します。関数がエラーを報告すると、パイプラインの実行が停止され、ユーザーにエラーが返されます。

“getInputsInfo” 関数

この関数は、予期される入力のメタデータに関する情報を返します。返された CustomNodeTensorInfo オブジェクトは、getModelMetadata 呼び出しの応答を作成するために使用されます。ユーザー要求の検証やパイプライン構成の検証にも使用されます。

カスタムノードは、入力データとカスタム・ノード・パラメーターに応じて動的なサイズの結果を生成できます。このような場合、関数 getInputsInfo は動的サイズの次元で値 0 を返す必要があります。可変解像度またはバッチサイズを入力できます。

“getOutputInfo” 関数

前の関数と似ていますが、出力のメタデータを定義します。

“release” 関数

この関数は、パイプライン処理の最後に OVMS によって呼び出されます。ノードの実行中に使用されたすべてのメモリー割り当てがクリアされます。execute 関数で出力メモリーを割り当てるために malloc が使用された場合、この関数は free を呼び出す必要があります。メモリープールが使用されている場合、この関数は事前に割り当てられたメモリーをプールに返す必要があります。OVMS は、いつ解放するか、どのバッファーを解放するか決定します。

“initialize” 関数

int initialize(void** customNodeLibraryInternalManager, const struct CustomNodeParam* params, int paramsCount);

この機能により、予測間で再利用するリソースの作成が可能になります。潜在的な使用例には、最適化された一時バッファー割り当てがあります。initialize の使用はオプションであり、カスタムノードが機能するのに必須ではありません。customNodeLibraryInternalManager 初期化する場合は、この関数内でインスタンス化する必要があります。初期化が失敗した場合、OVMS がエラーとして扱うように、0 ではないステータスを返す必要があります。

使用しない場合は、最小限のダミー実装が必要です。戻り値 0 はエラーがないことを意味します。

int initialize(void** customNodeLibraryInternalManager, const struct CustomNodeParam* params, int paramsCount) {
    return 0;
}

“deinitialize” 関数

int deinitialize(void* customNodeLibraryInternalManager);

この機能により、予測の間に使用されたリソースを破棄できます。deinitialize の使用はオプションであり、カスタムノードが機能するのに必須ではありません。deinitialize を使用する場合、customNodeLibraryInternalManager をここで破棄する必要があります。初期化解除が失敗した場合は、OVMS がエラーとして扱うように、0 以外のステータスを返す必要があります。

使用しない場合は、最小限のダミー実装が必要です。戻り値 0 はエラーがないことを意味します。

int deinitialize(void* customNodeLibraryInternalManager) {
    return 0;
}

OpenCV を使用

カスタム・ノード・ライブラリーは、静的または動的にリンクできるサードパーティーの依存関係を使用できます。簡素化のため、OVMS Docker イメージに含まれる OpenCV ライブラリーを使用できます。次のようなステートメントを追加するだけです。

#include "opencv2/core.hpp"

文字列のサポート

クライアントから文字列として送信された入力をカスタムノードで処理する場合、特別な考慮事項があります。このようなデータは、OVMS フロントエンドで受信されると、形状 [-1,-1] の 2D 配列に自動的に変換されます。この機能を使用するカスタムノードの例には、トークナイザーがあります。

入力

2 次元形状と U8 精度 OVMS を持つカスタムノードに文字列が送信されると、そのような入力を含む要求を受信した後、文字列をゼロでパディングされた形状 [文字列の数、最も長い文字列の長さ + 1] の 2 次元 U8 配列に変換します。例えば、3 つの文字列 [“String_123”, “”, “zebra”] のバッチは次のように変換されます。

['S', 't', 'r', 'i', 'n', 'g', '_', '1', '2', '3', 0,  // String_123
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,                       // ""
'z', 'e', 'b', 'r', 'a', 0, 0, 0, 0, 0, 0]             // "zebra"

出力

カスタムノード出力の名前に _string というサフィックスが付いている場合、その形状は 2 次元で、精度は U8 です。OVMS は、そのような出力のデータを、各行にゼロで埋められた文字列を含む配列として扱い、そのような出力のデータを文字列に自動的に変換します。
U8 配列の例:

['S', 't', 'r', 'i', 'n', 'g', '_', '1', '2', '3', 0,  // String_123
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,                       // ""
'z', 'e', 'b', 'r', 'a', 0, 0, 0, 0, 0, 0]             // "zebra"

上記は [“String_123”, “”, “zebra”] に変換されます。

ビルド

カスタム・ノード・ライブラリーは、任意のツールでコンパイルできます。すべてのビルド依存関係が含まれる Docker コンテナに基づいた例に従うことを推奨します。Makefile に記述されています。

テスト

カスタム・ライブラリーをテストする推奨方法は、OVMS 実行によるものです。

  • Makefile で構成された Docker コンテナを使用してライブラリーをコンパイルします。lib フォルダーにエクスポートされます。

  • 前の手順でコンパイルしたパスのカスタム・ノード・ライブラリーを使用してパイプライン構成を準備します。

  • OVMS Docker コンテナを起動します。

  • gRPC または REST クライアントを使用して、OVMS エンドポイントに要求を送信します。

  • OVMS サーバー上のログを分析します。

デバッグ手順については、OVMS 開発者ガイドを参照してください。

ビルトインのカスタムノード

OpenVINO モデルサーバーに加えて、イメージにはビルトインのカスタムノードも提供されています。これらはコンテナ内の /ovms/lib/custom_nodes ディレクトリーに存在し、構成ファイル内で参照できます。以下に、モデルサーバーの Docker イメージに埋め込まれたカスタムノードのリストを示します。

カスタムノード

コンテナ内の位置

east-resnet50 OCR カスタムノード

/ovms/lib/custom_nodes/libcustom_node_east_ocr.so

horizontal OCR カスタムノード

/ovms/lib/custom_nodes/libcustom_node_horizontal_ocr.so

インテルの Model Zooのオブジェクト検出カスタムノード

/ovms/lib/custom_nodes/libcustom_node_model_zoo_intel_object_detection.so

画像変換カスタムノード

/ovms/lib/custom_nodes/libcustom_node_image_transformation.so

カスタムノードを 1 つ追加

/ovms/lib/custom_nodes/libcustom_node_add_one.so

顔ブラー・カスタム・ノード

/ovms/lib/custom_nodes/libcustom_node_face_blur.so

例: ビルトインの水平 OCR カスタムノードconfig.json に含めると、次のようになります。

...
    "custom_node_library_config_list": [
        {
            "name": "ocr_image_extractor",
            "base_path": "/ovms/lib/custom_nodes/libcustom_node_horizontal_ocr.so"
        }
    ],
...

カスタムノードはこのパスですでに使用可能です。何かを構築したりコンテナにマウントする必要はありません。

単体テストには追加の例が含まれています。