ステートフル・モデルのサービング#

ステートレスとステートフル・モデル#

ステートレス・モデル#

ステートレス・モデルは、すべての推論要求を独立して処理し、連続する推論要求間の依存関係を認識しません。したがって、推論要求間の状態は維持されません。ステートレス・モデルの例としては、画像分類や物体検出の畳み込みニューラル・ネットワーク (CNN) が挙げられます。

ステートフル・モデル#

ステートフル・モデルは、連続する推論要求間の依存関係を認識します。次の推論が前の推論の結果に依存するように、推論要求間の状態を維持します。ステートフル・モデルの例としては、Long Short Term Memory (LSTM: 長短期記憶) などのオンライン音声認識モデルが挙げられます。


モデルサーバーのコンテキストでは、推論要求間でモデルが状態を維持する場合、モデルはステートフルであると見なされます。

一部のモデルは、データのシーケンス全体を入力として受け取り、内部でそのシーケンスの要素を反復処理し、反復間の状態を維持します。このようなモデルは、1 回の推論要求でシーケンス全体の推論を実行するため、ステートレスとみなされます。

ステートフル・モデルのロードと提供#

ステートフル・モデルを使用してモデルサーバーを実行#

OpenVINO モデルサーバーでのステートフル・モデルの提供は、ステートレス・モデルの提供と非常に似ています。唯一の違いは、ステートフル・モデルの場合、モデル設定で stateful フラグを設定する必要があることです。

  • rm_lstm4f からサンプルモデルをダウンロードして準備します。

mkdir models && cd models 
wget https://storage.openvinotoolkit.org/models_contrib/speech/2021.2/rm_lstm4f/rm_lstm4f.counts 
wget https://storage.openvinotoolkit.org/models_contrib/speech/2021.2/rm_lstm4f/rm_lstm4f.nnet 
wget https://storage.openvinotoolkit.org/models_contrib/speech/2021.2/rm_lstm4f/rm_lstm4f.mapping 
docker run -u $(id -u):$(id -g) -v $(pwd):/models:rw openvino/ubuntu20_dev:latest mo --framework kaldi --input_model /models/rm_lstm4f.nnet --counts /models/rm_lstm4f.counts --remove_output_softmax --output_dir /models/rm_lstm4f/1
  • コマンドラインからステートフル・モデルを使用して OVMS を起動します:

docker run -d -u $(id -u):$(id -g) -v $(pwd)/rm_lstm4f:/models/stateful_model -p 9000:9000 openvino/model_server:latest \ 
--port 9000 --model_path /models/stateful_model --model_name rm_lstm4f --stateful
  • 構成ファイル経由でステートフル・モデルを使用して OVMS を開始します:

echo '{ 
    "model_config_list":[ 
      { 
        "config": { 
            "name":"rm_lstm4f", 
            "base_path":"/models/stateful_model", 
            "stateful": true 
        } 
      } 
    ] 
}' >> config.json
docker run -d -u $(id -u):$(id -g) -v $(pwd)/rm_lstm4f:/models/stateful_model -v $(pwd)/config.json:/models/config.json -p 9000:9000 openvino/model_server:latest \ --port 9000 --config_path /models/config.json

オプションで、ステートフル・モデルに固有の追加パラメーターを設定することもできます。

ステートフル・モデルの構成オプション#

モデル構成:

オプション

値形式

説明

デフォルト値。

stateful

bool

true に設定すると、モデルはステートフルとしてロードされます。

false

idle_sequence_cleanup

bool

true に設定すると、モデルは定期的なシーケンス・クリーナー・スキャンの対象になります。
アイドルシーケンスのクリーンアップを参照してください。

true

max_sequence_number

uint32

モデルのインスタンスによって同時に処理できるシーケンスの数を決定します。

500

low_latency_transformation

bool

true に設定すると、モデルサーバーはモデルの読み込み時に低レイテンシーの変換を適用します。

false

注: idle_sequence_cleanupmax_sequence_number、および low_latency_transformation 変換を設定するには、stateful を true に設定する必要があります。

サーバー構成:

オプション

値形式

説明

デフォルト値。

sequence_cleaner_poll_wait_minutes

uint32

次のシーケンス・クリーナー・スキャンの時間間隔 (分単位)。アイドルシーケンスのクリーンアップの対象となっており、最後のスキャン以降非アクティブなモデルのシーケンスは削除されます。値をゼロにすると、シーケンスクリーナーは無効になります。
アイドルシーケンスのクリーンアップを参照してください。

5

完全なセットアップを行うには、すべてのサーバーおよびモデルの構成オプションも参照してください。

ステートフル・モデルで推論を実行#

シーケンス処理のための特別な入力#

ステートフル・モデルは、相互に関連付けられ、一連の要求を形成する連続した推論要求に対して機能します。単一のステートフル・モデルは、一度に複数の独立したシーケンスを処理できます。モデルサーバーは、ステートフル・モデルに対する要求を受信すると、各要求を適切なシーケンスとそのメモリー状態にマップします。OVMS は、シーケンスの開始と終了も追跡して、システムリソースを適切に管理します。

ステートフル・モデルへの要求には、予測用のデータ以外に追加の入力を含める必要があります:

  • sequence_id - これは、シーケンスを識別する 64 ビットの符号なし整数です (モデル・インスタンスのスコープ内で一意です)。値 0 は、この入力を全く提供しないことと同じです。

  • sequence_control_input - これは、シーケンスの開始と終了を示す 32 ビットの符号なし整数です。受け入れられる値は次のとおりです:

    • 0 - 制御入力なし (効果はありません - この入力を全く提供しないのと同じです)

    • 1 - シーケンスの始まりを示します

    • 2 - シーケンスの終わりを示します

: モデルサーバーは、すべての応答に sequence_id も追加します。sequence_id 出力の名前と形式は、sequence_id 入力の場合と同じです。

sequence_idsequence_control_input は両方とも、1 つの要素配列 (shape:[1]) と適切な精度を持つテンソルとして提供されます。以下の gRPC と HTTP の例を参照してください

シーケンスを正常に推測するには、次のアクションを実行します:

  1. シーケンスの最初の要求を送信し、シーケンスの開始を通知します。

    シーケンスを開始するには、値 1 を持つ sequence_control_input を要求の入力に追加する必要があります。次のことも可能です:

    • 選択した値を使用して sequence_id を追加するか、

    • sequence_id を 0 で追加するか、sequence_id を追加しません。この場合、モデルサーバーはシーケンスに一意の ID を提供し、それが出力に追加されるため、それを読み取って次の要求で使用することができます。

    指定された sequence_id がすでに占有されている場合、OVMS は競合を避けるためエラーを返します。

  2. 最後の要求を除く残りの要求を送信します。

    シーケンスの途中で要求を送信するには、シーケンスの sequence_id を追加する必要があります。この場合、sequence_id は必須であり、この入力を提供しないこと、またはその値を 0 に設定することは許可されません。

    この場合、sequence_control_input は空または 0 でなければなりません。

  3. シーケンス内の最後の要求を送信し、シーケンスの終了を通知します。

    シーケンスを終了するには、値 2 を持つ sequence_control_input を要求の入力に追加する必要があります。シーケンスの sequence_id も追加する必要があります。この場合、sequence_id は必須であり、この入力を提供しないこと、またはその値を 0 に設定することは許可されません。

gRPC による推論#

gRPC を介したステートフル・モデルの推論は、ステートレス・モデルの推論と非常に似ています (gRPC API を参照)。違いは、ステートフル・モデルへの要求には、適切なシーケンス処理に必要な情報を含む追加の入力が含まれている必要があることです。

sequence_idsequence_control_inputTensorProtos として gRPC リクエスト入力に追加する必要があります。

  • sequence_id モデルの場合、サーバーは tensor proto uint64_val フィールドに 1 つの値を期待します。

  • sequence_control_input モデルの場合、サーバーは tensor proto uint32_val フィールドに 1 つの値を期待します。

両方の入力で TensorShape が [1] に設定され、適切な DataType が必要です:

  • DT_UINT64 (sequence_id 用)

  • DT_UINT32 (sequence_control_input 用)

例: (Python tensorflow および tensorflow-serving-api パッケージを使用):

... 
import grpc 

from tensorflow_serving.apis import prediction_service_pb2_grpc 
from tensorflow import make_tensor_proto, make_ndarray, expand_dims 
from tensorflow_serving.apis import predict_pb2 

...SEQUENCE_START = 1 
SEQUENCE_END = 2 
sequence_id = 10 

channel = grpc.insecure_channel("localhost:9000") 
stub = prediction_service_pb2_grpc.PredictionServiceStub(channel) 

request = predict_pb2.PredictRequest() 
request.model_spec.name = "stateful_model" 

""" 
Add inputs with data to infer 
""" 

################ ステートフルな特定の入力を追加 ############### 
################ カスタム ID による開始シーケンス ############### 

request.inputs['sequence_control_input'].CopyFrom( 
               make_tensor_proto([SEQUENCE_START], dtype="uint32")) 
request.inputs['sequence_id'].CopyFrom( 
               make_tensor_proto([sequence_id], dtype="uint64")) 

################ ID なしの開始シーケンス #################### 

request.inputs['sequence_control_input'].CopyFrom( 
              make_tensor_proto([SEQUENCE_START], dtype="uint32")) 

################ 非制御要求 ########################### 

request.inputs['sequence_id'].CopyFrom( 
              make_tensor_proto([sequence_id], dtype="uint64")) 

################ 終了シーケンス ########################## 

request.inputs['sequence_control_input'].CopyFrom( 
              make_tensor_proto([SEQUENCE_END], dtype="uint32")) 
request.inputs['sequence_id'].CopyFrom( 
              make_tensor_proto([sequence_id], dtype="uint64")) 

##################################################### 

# OVMS にリクエストを送信し応答を受け取る 
response = stub.Predict(request, 10.0) 

# レスポンス変数には、response.outputs の sequence_id だけでなく、モデル出力 (推論結果) も含まれるようになりました。 

# レスポンスからシーケンス ID を取得 
sequence_id = response.outputs['sequence_id'].uint64_val[0]

HTTP による推論#

HTTP を介したステートフル・モデルの推論は、ステートレス・モデルの推論と非常に似ています (REST API を参照)。違いは、ステートフル・モデルへの要求には、適切なシーケンス処理に必要な情報を含む追加の入力が含まれている必要があることです。

sequence_id および sequence_control_input は、JSON 本文の inputs フィールドに新しい key:value のペアを追加することによって、HTTP リクエストに追加する必要があります。

どちらの入力でも、値は 1 次元配列内の単一の数値である必要があります。

例: (Python 要求パッケージを使用):

... 
import json 
import requests 
...
SEQUENCE_START = 1 
SEQUENCE_END = 2 
sequence_id = 10 

inputs = {} 

""" 
Add inputs with data to infer 
""" 

################ ステートフルな特定の入力を追加 ################# 
################ カスタム ID による開始シーケンス ################# 

inputs['sequence_control_input'] = [int(SEQUENCE_START)] 
inputs['sequence_id'] = [int(sequence_id)] 

################ ID なしの開始シーケンス ###################### 

inputs['sequence_control_input'] = [int(SEQUENCE_START)] 

################ 非制御要求 ############################# 

inputs['sequence_id'] = [int(sequence_id)] 

################ 終了シーケンス ########################### 

inputs['sequence_control_input'] = [int(SEQUENCE_END)] 
inputs['sequence_id'] = [int(sequence_id)] 

###################################################### 

# リクエストの準備 
signature = "serving_default" 
request_body = json.dumps({"signature_name": signature,'inputs': inputs}) 

# OVMS にリクエストを送信し応答を受け取る 
response = requests.post("localhost:5555/v1/models/stateful_model:predict", data=request_body) 

# レスポンスを解析 
response_body = json.loads(response.text) 

# response_body 変数には、response_body["outputs"] のシーケンス ID だけでなく、モデル出力 (推論結果) も含まれるようになりました。 

# レスポンスからシーケンス ID を取得 
sequence_id = response_body["outputs"]["sequence_id"]

エラーコード#

要求が無効であるか、処理できなかった場合、ステートフル・モデルでの推論特有のエラーが発生することが予想されます:

説明

gRPC

HTTP

指定された ID を持つシーケンスは存在しません。

NOT_FOUND

404 NOT FOUND

指定された ID を持つシーケンスはすでに存在します。

ALREADY_EXISTS

409 CONFLICT

サーバーは終了用に設定されたシーケンスの ID を持つ SEQUENCE START リクエストを受信しましたが、そのシーケンスの最後のリクエストはまだ処理中です。

FAILED_PRECONDITION

412 PRECONDITION FAILED

最大シーケンス数に達しました。新しいシーケンスを作成できませんでした。

UNAVAILABLE

503 SERVICE UNAVAILABLE

リクエスト入力にシーケンス ID が指定されていません。

INVALID_ARGUMENT

400 BAD REQUEST

シーケンス制御入力の予期しない値です。

INVALID_ARGUMENT

400 BAD REQUEST

予期されたテンソル・プロト・フィールド uint64_val でシーケンス ID が見つかりませんでした。

INVALID_ARGUMENT

N/A

予期されたテンソル・プロト・フィールド uint32_val でシーケンス ID が見つかりませんでした。

INVALID_ARGUMENT

N/A

特殊な入力プロトにはテンソル形状情報が含まれません。

INVALID_ARGUMENT

N/A

アイドルシーケンスのクリーンアップ#

一度開始されたシーケンスは、接続の切断などの何らかの理由でドロップされる可能性があります。この場合、モデルサーバーは SEQUENCE_END 信号を受信せず、シーケンスリソースを解放しません。アイドル状態のシーケンスが無期限に保持されるのを防ぐために、モデルサーバーは、ステートフル・モデルを定期的にスキャンし、そのシーケンスが最近有効な推論要求を受信したかどうかをチェックするシーケンス・クリーナー・スレッドを起動します。そうでない場合、そのようなシーケンスは削除され、そのリソースが解放され、ID が再利用できるようになります。

2 つのパラメーターはシーケンスのクリーンアップを制御します。1 つは、次のスキャン間の時間間隔の値を保持する sequence_cleaner_poll_wait_minutes です。2 つの連続したチェックの間に、特定のシーケンス ID を持つ有効な要求が 1 つも存在しなかった場合、シーケンスはアイドル状態とみなされ、削除されます。

sequence_cleaner_poll_wait_minutes はサーバー・パラメーターであり、すべてのモデルに共通です。デフォルトでは、2 回の連続したクリーナースキャン間の時間は 5 分に設定されています。この値を 0 に設定すると、シーケンスクリーナーが無効になります。

ステートフル・モデルは、アイドルシーケンスのクリーンアップの対象となる場合と、対象とならない場合があります。これは、idle_sequence_cleanup パラメーターを使用してモデルごとに設定できます。true に設定すると、シーケンスクリーナーがそのモデルをチェックします。それ以外の場合、シーケンスクリーナーはそのモデルをスキップし、アクティブでないシーケンスは削除されません。デフォルトでは、この値は true に設定されます。

既知の問題#

OVMS のステートフル・モデルの使用には制限があります:

  • ターゲットデバイスとして CPU のみを使用した推論実行をサポートします。

  • メモリーレイヤーを備えた Kaldi モデルとテンソル・イテレーターを備えた非 Kaldi モデルをサポートします。OpenVINO でのステートフル・ネットワークの表現については、ステートフル・ネットワークに関するドキュメントを参照してください。

  • 自動バッチサイズと形状は、ステートフル・モデルでは使用できません

  • ステートフル・モデルのインスタンスは DAG では使用できません

  • 要求の順序は、単一のクライアントが後続の要求を同期的に送信する場合にのみ保証されます。同じシーケンスを同時に操作すると、結果の精度に悪影響を及ぼす可能性があります。

  • モデル構成の変更によりステートフル・モデルのインスタンスがリロードされると、進行中のシーケンスがすべて削除されます。

  • モデルタイプは実行時に変更できません。ステートフル・フラグの切り替えは拒否されます。