OpenVINO モデルサーバーでの Python サポート - クイックスタート#

OpenVINO モデルサーバーを使用すると、ユーザーは Python でカスタム処理ノードを作成できるため、ノードに到達するデータで何が起こるか、またそこから何が出力されるかを完全に制御できます。

このクイックスタートでは、文字列を予期し、同じ文字列をすべて大文字で返す単一のカスタム Python コードを使用して提供可能なサービスを作成します。

この機能の詳細については、ドキュメントを参照してください。

これを行うには、次の手順に従います:

  1. ワークスペースの準備

  2. サーバー向けに Python コードを記述

  3. グラフ設定ファイルの準備

  4. サーバー構成ファイルの準備

  5. OpenVINO モデルサーバーのデプロイ

  6. クライアント・アプリケーションの作成

  7. クライアントから要求送信

ステップ 1: ワークスペースの準備#

すべての作業を新しい workspace ディレクトリーで実行しましょう。次のサブディレクトリーも作成します:

  • workspace/models (デプロイメントにマウントされるカタログ)

  • workspace/models/python_model (Python サーバブル仕様のカタログ)

これらを一度に行うことができます:

mkdir -p workspace/models/python_model && cd workspace

文字列内のすべての文字を大文字に変更するのは基本的な例であるため、追加 Python パッケージを含まない基本 Python 対応モデル・サーバー・イメージで十分です。外部モジュールが必要な場合は、それらを手動でイメージに追加する必要があります。この例では、Docker Hub で公開されている openvino/model_server:latest イメージを使用します。

クライアント・モジュールも必要になるため、環境に必要な依存関係をインストールします:

pip3 install tritonclient[grpc]

ステップ 2: サーバー向けに Python コードを記述#

サーバー側のコードから始めます。ここでは、OvmsPythonModel クラスを実装します。モデルサーバーは、少なくとも execute メソッドが実装されていることを期待します。

基本的な構成では、モデルが要求を受信するたびに、execute メソッドが呼び出されます。サーバーはその要求から入力を読み取り、inputs 引数として execute 関数に渡します。

inputspyovms.Tensor オブジェクトのリストです。この場合、入力は 1 つだけであるため、コードは次のように開始できます:

input_data = inputs[0]

input_data は、データと、形状やデータタイプなどのメタデータを保持する pyovms.Tensor オブジェクトです。この時点で、どのような種類のデータを受信するか決定する必要があります。

文字列を操作するので、input_data が UTF-8 でエンコードされた文字列であると想定します。その場合、input_data からバイトのインスタンスを作成し、実際の文字列にデコードできます。

text = bytes(input_data).decode()

また、データをどの形式で返すかを決定できます。UTF-8 でエンコードされた文字列を返すのが理にかなっています:

output_data = text.upper().encode()

出力は pyovms.Tensor のリストであることが想定されるため、出力を適切な出力名 (この場合は大文字) で pyovms.Tensor にパックし、それをリストとして返す必要があります。

完全なコードは次のようになります:

from pyovms import Tensor 

class OvmsPythonModel: 

    def execute(self, inputs: list): 
        input_data = inputs[0] 
        text = bytes(input_data).decode() 
        output_data = text.upper().encode() 
        return [Tensor("uppercase", output_data)]

このコードを使用して model.py ファイルを作成し、workspace/models/python_model に保存します。

echo ' 
from pyovms import Tensor 

class OvmsPythonModel: 

    def execute(self, inputs: list): 
        input_data = inputs[0] text = bytes(input_data).decode() 
        output_data = text.upper().encode() 
        return [Tensor("uppercase", output_data)] 
' >> models/python_model/model.py

ステップ 3: グラフ設定ファイルの準備#

OpenVINO モデルサーバーでの Python ロジックの実行は、MediaPipe グラフを介してサポートされています。つまり、処理フローに合わせてグラフ定義を準備する必要があります。その場合、ノードが 1 つだけ (Python ノード) のグラフで十分です。workspace/models/python_model カタログに適切な graph.pbtxt ファイルを作成します:

echo ' 
input_stream: "OVMS_PY_TENSOR:text" 
output_stream: "OVMS_PY_TENSOR:uppercase" 

node { 
  name: "pythonNode" 
  calculator: "PythonExecutorCalculator" 
  input_side_packet: "PYTHON_NODE_RESOURCES:py" 
  input_stream: "INPUT:text" 
  output_stream: "OUTPUT:uppercase" 
  node_options: { 
    [type.googleapis.com / mediapipe.PythonExecutorCalculatorOptions]: { 
      handler_path: "/models/python_model/model.py" 
    } 
  } 
}' >> models/python_model/graph.pbtxt

上記の構成ファイルは、PythonExecutorCalculator を使用する単一の Python ノードを含むグラフを作成し、入力と出力を設定し、handler_path 内の Python コードの場所を指定します。

input_streamoutput_stream (最初の 2 行) は、グラフの入力と出力を定義します。:右側の名前は、要求と応答で使用される名前です。この場合:

  • 入力: “text”

  • 出力: “uppercase”

ノードレベルで input_streamoutput_stream を確認できます。これらは、execute メソッドコード内の名前付けを参照します。前のステップの execute 実装で、出力テンソルに “uppercase” という名前を付けていることに注目してください。

この場合、グラフとノードレベルの両方のストリームの名前は全く同じになります。これは、グラフ入力はノード入力でもあり、ノード出力もグラフ出力であることを意味します。

input_side_packet 値は、グラフ・インスタンス間でリソースを共有するためにモデルサーバーによって使用される内部フィールドです。変更してはなりません。

ステップ 4: サーバー構成ファイルの準備#

構成の最後は、モデルサーバー構成ファイルです。次の内容を含む config.jsonworkspace/models に作成します:

echo ' 
{ 
    "model_config_list": [], 
    "mediapipe_config_list": [ 
    { 
        "name":"python_model", 
        "graph_path":"/models/python_model/graph.pbtxt" 
    } 
    ] 
} 
' >> models/config.json

これにより、OpenVINO モデルサーバーに、指定された名前 python_model でグラフを提供するように指示されます。

ステップ 5: OpenVINO モデルサーバーのデプロイ#

サーバーを実行する前に、デプロイに必要なファイルがすべて揃っているかどうかを確認します。workspace/models のカタログがコンテナにマウントされるので、その内容を確認します:

tree models 
models 
├── config.json 
└── python_model 
     ├── graph.pbtxt 
     └── model.py

サーバーを実行します:

docker run -it --rm -p 9000:9000 -v $PWD/models:/models openvino/model_server:latest --config_path /models/config.json --port 9000

ステップ 6: クライアント・アプリケーションの作成#

Python モデルがデプロイされたので、もう一方のエッジであるクライアント・アプリケーションに集中できます。クライアントを作成する場合、サーバー側のコードがどのように見えるか留意してください。サーバー側のコードは補完的である必要があります。

まず、ポート 9000 で使用可能な gRPC インターフェイスを使用して、localhost でホストされているサーバーに接続します:

import tritonclient.grpc as grpcclient 
client = grpcclient.InferenceServerClient("localhost:9000")

文字列を送信するため、文字列を作成してサーバー側のコードで期待する UTF-8 にエンコードします。

data = "Make this text uppercase.".encode()

次に、データをサーバーに送信する gRPC 構造にパックします。

infer_input = grpcclient.InferInput("text", [len(data)], "BYTES") 
infer_input._raw_content = data

これで、名前 “text”、形状 [len(data)] (len(data) はエンコードされたバイト数)、データタイプが “BYTES” のグラフ入力に対応する InferInput オブジェクトが作成されます。データは raw_content フィールドに書き込まれます。これらの値はすべてサーバー側でアクセスできます。

最後の部分は、このデータをサーバーに送信します:

results = client.infer("python_model", [infer_input]) 
print(results.as_numpy("uppercase").tobytes().decode())

ここでは、infer_input を要求にパックし、uppercase_model というサーブブルに送信します。

サーバーは、UTF-8 でエンコードされた文字列を含む出力で応答することが期待されているため、2 行目でそれを読み取り、実際の文字列にデコードして出力します。

コード全体を workspace 内の client.py ファイルに保存します:

echo ' 
import tritonclient.grpc as grpcclient 
client = grpcclient.InferenceServerClient("localhost:9000") 
data = "Make this text uppercase.".encode() 
infer_input = grpcclient.InferInput("text", [len(data)], "BYTES") 
infer_input._raw_content = data 
results = client.infer("python_model", [infer_input]) 
print(results.as_numpy("uppercase").tobytes().decode()) 
' >> client.py

ステップ 7: クライアントから要求送信#

モデルサーバーを起動して実行したらテキストを送信します: "Make this text uppercase."

workspace のカタログから client.py を実行するだけで、結果が表示されます:

python3 client.py 
MAKE THIS TEXT UPPERCASE.