Triton - Concurrent Model Execution (モデル並行実行) の検証
Triton Inference Server の動作検証シリーズの第 4 弾.今回は Triton の売りの一つであるモデルの並行実行について,動作条件などを確認していく.
[1]
Concurrent Model Execution(公式ドキュメントの要約)
Triton では,1 つのシステム上で,複数のモデルを並列実行 (execute in parallel) することができる.ここでいう"モデル"は,異なる種類のモデルや同じモデルの複数インスタンス,またはその組み合わせも含む.
推論リクエストがない状態で,2 種類のモデルに対する推論リクエストが同時に来た場合,Triton は直ちに両方を GPU 上にスケジュールし,GPU のハードウェアスケジューラが 2 つのリクエストに対する処理を始める.
Triton では,1 つのモデルの並列実行数を instance group で指定できる.デフォルトでは,各モデルのインスタンスは利用可能な GPU 上に 1 つずつ配置される[2].インスタンス数間ではリクエストは直ちに並列処理される.インスタンスを超えるリクエストが来た場合は,前のリクエストのいずれかが終わるまで待つ.
今回の検証課題
Ensemble Models の解説の図[3]では,2 つのモデルが並列実行されているような書き方がされている.また上の説明文でも,Concurrent/Parallel という単語が使用されている.具体的にどのような形式でモデルが並列実行 (or 並行実行?) されるのか確認したい.また,並列実行される条件も確認する.
予想: Ensemble Model Scheduler はモデルの入力が準備できたかどうかをモニタリングして,すべて準備ができたらリクエストを送る (event driven).config.pbtxt
に記述されるモデルの入出力 (key
) をもとに,パイプラインの分岐が決定されるのではないか?
検証方法
プロファイラからモデルの挙動が把握しやすいように,Conv2D 1/2/3 層で構成される 3 種類の ONNX モデルの Ensemble Models で実行する.Ensemble Models の記述方法は以下の 3 種類.これらのモデルで構成されるパイプラインを実行し,そのプロファイラの結果から挙動を分析する.
- Pipeline 1: モデルの出力の key を,次のモデルの入力の key に指定. (明示的な直列パターン)
-
Pipeline 2: Conv2d 1 層と 3 層のモデルの入力 key に,パイプラインの入力 (
input
) を指定.Conv2d 3 層のモデルの入力には,Conv 2d 2 層のモデルの入力に Conv2d 1 層のモデルの出力を指定. -
Pipeline 3: 全てのモデルの入力の key に,パイプラインの入力 (
input
) を指定.
Ensemble Models におけるモデルの接続パターン (= Key のマッピング)は以下の通り.これらのパイプラインを実行し.Profiler (NVIDIA Nsight Systems)を用いて動作状態を確認した.
Pipeline Architecture
実装
詳細な実装は GitHub を参照してほしい (読みやすいようにリファクタリング継続中).
ここでは,config.pbtxt
の ensemble_scheduling
に絞って説明する.
config.pbtxt
for Pipeline 1
各モデルの出力 (output_map.value
) を,次のモデルの入力 (input_map.value
) に設定することで,モデルを接続する.
...
ensemble_scheduling {
step [
{
model_name: "simple_cnn_l1"
model_version: -1
input_map {
key: "input_image"
value: "input_image"
}
output_map {
key: "output_image"
value: "output_image_m1"
}
},
{
model_name: "simple_cnn_l2"
model_version: -1
input_map {
key: "input_image"
value: "output_image_m1"
}
output_map {
key: "output_image"
value: "output_image_m2"
}
},
{
model_name: "simple_cnn_l3"
model_version: -1
input_map {
key: "input_image"
value: "output_image_m2"
}
output_map {
key: "output_image"
value: "output_image_m3"
}
}
]
}
config.pbtxt
for Pipeline 2
最初 1 つのモデルは,クライアントからの入力のみに依存し,3 つ目のモデルは 2 つ目のモデルの出力を受け取る.
ensemble_scheduling {
step [
{
model_name: "simple_cnn_l1"
model_version: -1
input_map {
key: "input_image"
value: "input_image"
}
output_map {
key: "output_image"
value: "output_image_m1"
}
},
{
model_name: "simple_cnn_l2"
model_version: -1
input_map {
key: "input_image"
value: "input_image"
}
output_map {
key: "output_image"
value: "output_image_m2"
}
},
{
model_name: "simple_cnn_l3"
model_version: -1
input_map {
key: "input_image"
value: "output_image_m2"
}
output_map {
key: "output_image"
value: "output_image_m3"
}
}
]
}
config.pbtxt
for Pipeline 3
最後は,input
の value を全てのモデルの入力 (input_map.value
) に指定する.各モデルは入力にのみ依存する.
...
input [
{
name: "input_image"
data_type: TYPE_FP32
dims: [ -1, 3, 1080, 1920 ]
}
]
...
ensemble_scheduling {
step [
{
model_name: "simple_cnn_l1"
model_version: -1
input_map {
key: "input_image"
value: "input_image"
}
output_map {
key: "output_image"
value: "output_image_m1"
}
},
{
model_name: "simple_cnn_l2"
model_version: -1
input_map {
key: "input_image"
value: "input_image"
}
output_map {
key: "output_image"
value: "output_image_m2"
}
},
{
model_name: "simple_cnn_l3"
model_version: -1
input_map {
key: "input_image"
value: "input_image"
}
output_map {
key: "output_image"
value: "output_image_m3"
}
}
]
}
実験結果
Pipeline 1 (Sequential)
このパターンでは,前のモデルの出力キーが,次のモデルの入力にマップされている.つまり,モデルが CNN 1 層 -- 2 層 -- 3 層の順に順番に繋がれることを意味する.プロファイラの結果からも想定通り,モデルが順番に実行されていることがわかる.
Profiling Pipeline 1 (Sequential)
補足: Conv2d の 1 層ごとに 1 つの CUDA カーネルが実行される.従って,実行されている畳み込みカーネルの個数を数えることで,どの Stream がどのモデルに対応しているか知ることができる.
Pipeline 2 (Partially Parallel)
Conv2d 1 層と 2 層のモデルがクライアントからのリクエストを受け取った直後に実行開始していることが,CPU 側の挙動 (Threads は以下の帯)からわかる.一方で,CUDA は Conv2d 1層のモデルが完了してから,Conv2d 2 層のモデルが実行されている.ただし,Conv2d 1 層モデルの出力値のコピーは,畳み込み演算の直後ではなく,Conv2d 2 層モデルの 1 層目の畳み込み終了後にコピーされている.Conv2d3 層のモデルは CPU Thread と CUDA Process ともに Conv2d 2 層のモデルが完全に終了してから開始していた.
Pipeline 3 (Full Parallel)
このパターンでは,クライアントからの入力が直接全てのモデルの入力にマップされる.
Request を受け取った時点で,全てのモデルの入力が揃い実行可能な状態になっていることが,Threads 配下(CPU 側)の帯からわかる.ただし,畳み込み演算など Kernel Process は,各モデルの計算が混ざりながら,順番に実行されている.つまり,ほぼ同時刻に 3 つのモデルが GPU のキューに入り,各モデルの 1 層目のみが順番に実行にされる,終了し次第 2 層目のみが順に実行される.おそらく,2 層は 1 層目の計算が終わってから GPU のキューに入れられている模様.
まとめ
- 実行状態
- CPU プロセスとして,モデルは並列実行
- CUDA Kernel レベルで見ると,モデルは並行実行.
- モデル並列実行の条件
- "Event Driven Scheduler"を採用しているので,入力が揃えばモデルが実行状態に入る.
-
ensemble_scheduling
において,同じ入力を必要とするモデルは,同じ入力のキーを設定すれば,自動的に並列実行される.
これまでは,モデルの計算自体も並列化されるのかと思っていたが,理解が甘かった.ここら辺の挙動を正しく理解するためには GPU の仕組みや,CUDA プログラミングを理解する必要がある.理解を深めるために後日勉強していきたい.
Ensemble Model に関する補足
本実験を行うためにドキュメントを読み直した際に,気づいたことをここにメモしておく.
- Esemble scheduler はイベントドリブンのスケジューラであるため,
instance_group
をサポートしていない.つまり,パイプライン単位でインスタンスを用意することはできない.個別のモデルでの設定が必要. - "The reason is that the ensemble scheduler itself is mainly an event-driven scheduler with very minimal overhead so its almost never the bottleneck of the pipeline." 「Ensemble model scheduler がパイプラインのボトルネックとなることはない」と書かれている.
-
(原文) Each such enabled parallel execution is referred to as an instance. By default, Triton gives each model a single instance for each available GPU in the system. インスタンス数 > GPU となった場合にどのような挙動をするか,今後検証したい. ↩︎
-
https://docs.nvidia.com/deeplearning/triton-inference-server/user-guide/docs/_images/ensemble_example0.png ↩︎
Discussion