OpenVINO モデルサーバーでの Python サポート - クイックスタート#
OpenVINO モデルサーバーを使用すると、ユーザーは Python でカスタム処理ノードを作成できるため、ノードに到達するデータで何が起こるか、またそこから何が出力されるかを完全に制御できます。
このクイックスタートでは、文字列を予期し、同じ文字列をすべて大文字で返す単一のカスタム Python コードを使用して提供可能なサービスを作成します。
この機能の詳細については、ドキュメントを参照してください。
これを行うには、次の手順に従います:
ワークスペースの準備
サーバー向けに Python コードを記述
グラフ設定ファイルの準備
サーバー構成ファイルの準備
OpenVINO モデルサーバーのデプロイ
クライアント・アプリケーションの作成
クライアントから要求送信
ステップ 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
関数に渡します。
inputs
は pyovms.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_stream
と output_stream
(最初の 2 行) は、グラフの入力と出力を定義します。:
の右側の名前は、要求と応答で使用される名前です。この場合:
入力: “text”
出力: “uppercase”
ノードレベルで input_stream
と output_stream
を確認できます。これらは、execute
メソッドコード内の名前付けを参照します。前のステップの execute
実装で、出力テンソルに “uppercase” という名前を付けていることに注目してください。
この場合、グラフとノードレベルの両方のストリームの名前は全く同じになります。これは、グラフ入力はノード入力でもあり、ノード出力もグラフ出力であることを意味します。
input_side_packet
値は、グラフ・インスタンス間でリソースを共有するためにモデルサーバーによって使用される内部フィールドです。変更してはなりません。
ステップ 4: サーバー構成ファイルの準備#
構成の最後は、モデルサーバー構成ファイルです。次の内容を含む config.json
を workspace/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.