PyTorch 1.4 Tutorials : PyTorch モデル配備 : TorchScript モデルを C++ でロードする (翻訳/解説)
翻訳 : (株)クラスキャット セールスインフォメーション
作成日時 : 01/24/2020 (1.4.0)
* 本ページは、PyTorch 1.4 Tutorials の以下のページを翻訳した上で適宜、補足説明したものです:
- Deploying PyTorch Models in Production : Loading a TorchScript Model in C++
* サンプルコードの動作確認はしておりますが、必要な場合には適宜、追加改変しています。
* ご自由にリンクを張って頂いてかまいませんが、sales-info@classcat.com までご一報いただけると嬉しいです。
PyTorch モデル配備 : TorchScript モデルを C++ でロードする
その名前が示唆するように、PyTorch への主要なインターフェイスは Python プログラミング言語です。Python が iteration のダイナミズムと容易さを必要とする多くのシナリオのためにふさわしく好まれる言語である一方で、Python のこれらの特質が的確に不都合である多くの状況が同様にあります。後者がしばしば当てはまる一つの環境はプロダクションです – 低レイテンシーと厳密な配備要件の世界です。プロダクション・シナリオのためには、C++ は非常に多くの場合選択される言語です、それを Java、Rust や Go のような別の言語にバインドするだけでさえも。以下のパラグラフでは既存の Python モデルからシリアライズされた表現へと進む PyTorch が提供するパスを概説します、この表現は Python への依存性なく、単に C++ からロードできて実行可能です。
ステップ 1 : PyTorch モデルを Torch Script に変換する
Python から C++ への PyTorch モデルの行程は Torch Script により可能になります、(これは) Torch Script コンパイラにより理解され、コンパイルされてシリアライズ可能な、PyTorch モデルの表現です。もし vanilla “eager” API で書かれた既存の PyTorch モデルから始めているのであれば、最初にモデルを Torch Script に変換しなければなりません。最も一般的なケースでは、下で議論されるように、これは少しの努力だけを必要とします。もし既に Torch Script モジュールを持つ場合には、このチュートリアルの次のセクションにスキップできます。
PyTorch モデルを Torch Script に変換する 2 つの方法があります。最初のものは tracing として知られています、そこではモデルの構造はサンプル入力を使用してそれを一度評価して、モデルを通したそれらの入力のフローを記録することにより捕捉されるメカニズムです。これは制御フローを制限的に利用するモデルに適しています。2 番目のアプローチは Torch Script コンパイラにそれが (Torch Script 言語により課せられた制約を条件に、) 貴方のモデルコードを直接パースしてコンパイルして良いことを知らせる明示的なアノテーションをモデルに追加します。
TIP: どちらを使用するかの更なるガイダンスに加えて、これらのメソッドの両者について完全なドキュメントを公式 Torch Script リファレンス で見つけることができます。
Torch Script を Tracing を通して変換する
PyTorch モデルを tracing を通して Torch Script に変換するには、サンプル入力とともにモデルのインスタンスを torch.jit.trace 関数に渡さなければなりません。これはモジュールの forward メソッドに埋め込まれたモデル評価の trace と共に torch.jit.ScriptModule オブジェクトを生成します :
import torch import torchvision # An instance of your model. model = torchvision.models.resnet18() # An example input you would normally provide to your model's forward() method. example = torch.rand(1, 3, 224, 224) # Use torch.jit.trace to generate a torch.jit.ScriptModule via tracing. traced_script_module = torch.jit.trace(model, example)
trace された ScriptModule は今では通常の PyTorch モジュールと完全に同じように評価できます :
In[1]: output = traced_script_module(torch.ones(1, 3, 224, 224)) In[2]: output[0, :5] Out[2]: tensor([-0.2698, -0.0381, 0.4023, -0.3010, -0.0448], grad_fn=<SliceBackward>)
Torch Script をアノテーションを通して変換する
貴方のモデルが制御フローの特定の形式を用いているようなある状況下では、モデルを直接 Torch Script で書いてそれに従ってモデルをアノテートすることを望むかもしれません。例えば、次の vanilla PyTorch モデルを持つとしましょう :
import torch class MyModule(torch.nn.Module): def __init__(self, N, M): super(MyModule, self).__init__() self.weight = torch.nn.Parameter(torch.rand(N, M)) def forward(self, input): if input.sum() > 0: output = self.weight.mv(input) else: output = self.weight + input return output
このモジュールの forward メソッドは入力に依拠する制御フローを使用しますので、tracing には適しません。代わりに、それを ScriptModule に変換するために、次のように torch.jit.script でモジュールをコンパイルする必要があります :
class MyModule(torch.nn.Module): def __init__(self, N, M): super(MyModule, self).__init__() self.weight = torch.nn.Parameter(torch.rand(N, M)) def forward(self, input): if input.sum() > 0: output = self.weight.mv(input) else: output = self.weight + input return output my_module = MyModule(10,20) sm = torch.jit.script(my_module)
nn.Module の幾つかのメソッドを除外する必要がある場合、というのはそれらが TorchScript がまだサポートしていない Python 特徴を使用しているためですが、それらを @torch.jit.ignore でアノテートできるでしょう。
my_module はシリアライゼーションへの準備ができている ScriptModule のインスタンスです。
ステップ 2: Script Module をファイルにシリアライズする
PyTorch モデルを tracing かアノテートすることによりひとたび手元に ScriptModule を持つならば、それをファイルにシリアライズする準備ができています。後で、このファイルから C++ 内にモジュールをロードして Python への依存なしにそれを実行することができます。例えば tracing サンプルで前に示された ResNet18 モデルをシリアライズすることを望むとします。このシリアライゼーションを遂行するには、単にモジュール上で save を呼び出してそれにファイル名を渡します :
traced_script_module.save("traced_resnet_model.pt")
これは貴方の作業ディレクトリに traced_resnet_model.pt ファイルを生成します。また my_module をシリアライズしたい場合には、my_module.save(“my_module_model.pt”) を呼び出します。今では Python の領域を正式に去り、そして C++ の範囲に渡る準備ができています。
ステップ 3: Script Module を C++ にロードする
シリアライズされた PyTorch モデルを C++ にロードするには、アプリケーションは LibTorch としても知られる、PyTorch C++ API に依拠しなければなりません。LibTorch ディストリビューションは共有ライブラリ、ヘッダファイルと CMake ビルド configuration ファイルのコレクションを含みます。CMake は LibTorch に依拠するための要件ではない一方で、それは推奨されるアプローチで将来に向けて良くサポートされます。このチュートリアルのためには、CMake and LibTorch を使用して minimal C++ アプリケーションを構築していきます、これはシリアライズされた PyTorch モデルを単純にロードして実行します。
Minimal C++ アプリケーション
モジュールをロードするためのコードを議論することから始めましょう。次が既に行ないます :
#include <torch/script.h> // One-stop header. #include <iostream> #include <memory> int main(int argc, const char* argv[]) { if (argc != 2) { std::cerr << "usage: example-app <path-to-exported-script-module>\n"; return -1; } torch::jit::script::Module module; try { // Deserialize the ScriptModule from a file using torch::jit::load(). module = torch::jit::load(argv[1]); } catch (const c10::Error& e) { std::cerr << "error loading the model\n"; return -1; } std::cout << "ok\n"; }
<torch/script.h> ヘッダはサンプルを実行するために必要な LibTorch ライブラリからの総ての関連する include を含みます。アプリケーションはシリアライズされた PyTorch ScriptModule へのファイルパスをその唯一のコマンドライン引数として受け取りそして (このファイルパスを入力として取る) torch::jit::load() 関数を使用してモジュールをデシリアライズすることに進みます。返しに torch::jit::script::Module オブジェクトを受け取ります。それをすぐにどのように実行するかを調べます。
LibTorch への依拠とアプリケーションの構築
上のコードを example-app.cpp と呼ばれるファイルにストアしたと仮定します。それをビルドするための最小限の CMakeLists.txt は次のように単純に見えます :
cmake_minimum_required(VERSION 3.0 FATAL_ERROR) project(custom_ops) find_package(Torch REQUIRED) add_executable(example-app example-app.cpp) target_link_libraries(example-app "${TORCH_LIBRARIES}") set_property(TARGET example-app PROPERTY CXX_STANDARD 14)
example アプリケーションをビルドするために必要な最後のものは LibTorch ディストリビューションです。PyTorch web サイト上の ダウンロードページ から最新のステーブル・リリースを常につかむことができます。最新アーカイブをダウンロードして unzip する場合、次のディレクトリ構造を持つフォルダを受け取るはずです :
libtorch/ bin/ include/ lib/ share/
- lib/ フォルダはリンクしなければならない共有ライブラリを含みます、
- include/ フォルダはプログラムが include する必要があるヘッダファイルを含みます、
- share/ フォルダは上の単純な find_package(Torch) コマンドを有効にする必要な CMake configuration を含みます。
最後のステップはアプリケーションをビルドすることです。このために、example ディレクトリはこのようにレイアウトされます :
example-app/ CMakeLists.txt example-app.cpp
example-app/ フォルダの中からアプリケーションをビルドするために次のコマンドを実行できます :
mkdir build cd build cmake -DCMAKE_PREFIX_PATH=/path/to/libtorch .. cmake --build . --config Release
ここで /path/to/libtorch は unzip された LibTorch ディストリビューションへのフルパスであるべきです。総てが上手くいけば、それはこのように見えるでしょう :
root@4b5a67132e81:/example-app# mkdir build root@4b5a67132e81:/example-app# cd build root@4b5a67132e81:/example-app/build# cmake -DCMAKE_PREFIX_PATH=/path/to/libtorch .. -- The C compiler identification is GNU 5.4.0 -- The CXX compiler identification is GNU 5.4.0 -- Check for working C compiler: /usr/bin/cc -- Check for working C compiler: /usr/bin/cc -- works -- Detecting C compiler ABI info -- Detecting C compiler ABI info - done -- Detecting C compile features -- Detecting C compile features - done -- Check for working CXX compiler: /usr/bin/c++ -- Check for working CXX compiler: /usr/bin/c++ -- works -- Detecting CXX compiler ABI info -- Detecting CXX compiler ABI info - done -- Detecting CXX compile features -- Detecting CXX compile features - done -- Looking for pthread.h -- Looking for pthread.h - found -- Looking for pthread_create -- Looking for pthread_create - not found -- Looking for pthread_create in pthreads -- Looking for pthread_create in pthreads - not found -- Looking for pthread_create in pthread -- Looking for pthread_create in pthread - found -- Found Threads: TRUE -- Configuring done -- Generating done -- Build files have been written to: /example-app/build root@4b5a67132e81:/example-app/build# make Scanning dependencies of target example-app [ 50%] Building CXX object CMakeFiles/example-app.dir/example-app.cpp.o [100%] Linking CXX executable example-app [100%] Built target example-app
先に作成した trace された ResNet18 モデル traced_resnet_model.pt へのパスを結果としての example-app バイナリに供給すれば、友好的に "ok" で報いられるはずです。注意してください、このサンプルを my_module_model.pt で実行しようとすると、貴方の入力は非互換な shape であるというエラーを得るでしょう。my_module_model.pt は 4D の代わりに 1D を想定しています。
root@4b5a67132e81:/example-app/build# ./example-app/traced_resnet_model.pt ok
ステップ 4: Script Module を C++ で実行する
シリアライズされた ResNet18 を C++ 内に成功的にロードした今、それを実行することからは単に数行のコード離れているだけです!それらの行を C++ アプリケーションの main() 関数に追加しましょう :
// Create a vector of inputs. std::vector<torch::jit::IValue> inputs; inputs.push_back(torch::ones({1, 3, 224, 224})); // Execute the model and turn its output into a tensor. at::Tensor output = module.forward(inputs).toTensor(); std::cout << output.slice(/*dim=*/1, /*start=*/0, /*end=*/5) << '\n';
最初の 2 行はモデルへの入力をセットアップします。torch::jit::IValue (型消去された値の型 script::Module メソッドが受け取り返します) のベクトルを作成して単一の入力を追加します。入力 tensor を作成するため、torch::ones() を使用します、C++ API の torch.ones と等値です。それから script::Module の forward メソッドを実行し、作成した入力ベクトルをそれに渡します。返しに新しい IValue を得ます、これを toTensor() を呼び出して tensor に変換します。
TIP: 一般の torch::ones と PyTorch C++ API のような関数について学習するには、https://pytorch.org/cppdocs のドキュメントを参照してください。PyTorch C++ API は近い将来 Python API との等価を提供し、ちょうど Python でのように tensor を更に操作して処理することを可能にします。
最後の行は、出力の最初の 5 項目をプリントしています。このチュートリアルの前の方で Python のモデルに同じ入力を供給しましたので、理想的には同じ出力を見るはずです。アプリケーションを再コンパイルして同じシリアライズされたモデルでそれを実行して試してみましょう :
root@4b5a67132e81:/example-app/build# make Scanning dependencies of target example-app [ 50%] Building CXX object CMakeFiles/example-app.dir/example-app.cpp.o [100%] Linking CXX executable example-app [100%] Built target example-app root@4b5a67132e81:/example-app/build# ./example-app traced_resnet_model.pt -0.2698 -0.0381 0.4023 -0.3010 -0.0448 [ Variable[CPUFloatType]{1,5} ]
参照のため、Python での前の出力は :
tensor([-0.2698, -0.0381, 0.4023, -0.3010, -0.0448], grad_fn=)
Looks like a good match!
TIP: モデルを GPU メモリに移すには、model.to(at::kCUDA); を書くことができます。CUDA メモリ内の新しい tensor を返す tensor.to(at::kCUDA) を呼び出してモデルへの入力が CUDA メモリにあることを確実にしてください。
ステップ 5: ヘルプを得て API を調べる
このチュートリアルは Python から C++ への PyTorch モデルのパスの一般的な理解を願わくば貴方に授けました。このチュートリアルで説明されるコンセプトで、vanilla, "eager" Python モデルから、Python でコンパイルされた ScriptModule、ディスク上のシリアライズされたファイルそして - ループを閉じるために - 実行可能な script::Module へと進むことができるはずです。
もちろん、カバーしていない多くのコンセプトがあります。例えば、ScriptModule を C++ か CUDA で実装されたカスタム演算子で拡張し、そしてこのカスタム演算子を (C++ プロダクション環境にロードされた) ScriptModule 内で実行することを貴方は望むことを見い出すかもしれません。良いニュースは: これは可能です、そして上手くサポートされます!今のところは、examples のためのこのフォルダを調べることができます、そして短くチュートリアルをフォローします。当面は、次のリンクが一般に役立ちます :
- Torch Script リファレンス: https://pytorch.org/docs/master/jit.html
- PyTorch C++ API ドキュメント: https://pytorch.org/cppdocs/
- PyTorch Python API ドキュメント: https://pytorch.org/docs/
以上