PyTorch 1.8 チュートリアル : PyTorch モデル配備 : TorchScript モデルを C++ でロードする (翻訳/解説)
翻訳 : (株)クラスキャット セールスインフォメーション
作成日時 : 05/01/2021 (1.8.1+cu102)
* 本ページは、PyTorch 1.8 Tutorials の以下のページを翻訳した上で適宜、補足説明したものです:
- Deploying PyTorch Models in Production : Loading a TorchScript Model in C++
* サンプルコードの動作確認はしておりますが、必要な場合には適宜、追加改変しています。
* ご自由にリンクを張って頂いてかまいませんが、sales-info@classcat.com までご一報いただけると嬉しいです。
スケジュールは弊社 公式 Web サイト でご確認頂けます。
- お住まいの地域に関係なく Web ブラウザからご参加頂けます。事前登録 が必要ですのでご注意ください。
- ウェビナー運用には弊社製品「ClassCat® Webinar」を利用しています。
人工知能研究開発支援 | 人工知能研修サービス | テレワーク & オンライン授業を支援 |
PoC(概念実証)を失敗させないための支援 (本支援はセミナーに参加しアンケートに回答した方を対象としています。) |
◆ お問合せ : 本件に関するお問い合わせ先は下記までお願いいたします。
株式会社クラスキャット セールス・マーケティング本部 セールス・インフォメーション |
E-Mail:sales-info@classcat.com ; WebSite: https://www.classcat.com/ ; Facebook |
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 リファレンス で見つけることができます。
Tracing を通して Torch Script に変換する
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=)
アノテーションを通して 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 に変換します。モジュールを 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 でアノテートできるでしょう。
sm はシリアライゼーションのための準備ができている ScriptModule のインスタンスです。
ステップ 2: Script Module をファイルにシリアライズする
PyTorch モデルを tracing かアノテートすることによりひとたび手元に ScriptModule を持つならば、それをファイルにシリアライズする準備ができています。後で、このファイルから C++ 内にモジュールをロードして Python への依存なしにそれを実行することができます。例えば tracing サンプルで前に示された ResNet18 モデルをシリアライズすることを望むとします。このシリアライゼーションを遂行するには、単純にモジュール上で save を呼び出してそれにファイル名を渡します :
traced_script_module.save("traced_resnet_model.pt")
これは貴方の作業ディレクトリに traced_resnet_model.pt ファイルを生成します。また sm をシリアライズしたい場合には、sm.save(“my_module_model.pt”) を呼び出します。今では Python の領域 (= realm) を正式に去り、そして C++ の範囲へと渡る準備ができています。
ステップ 3: Script Module を C++ にロードする
シリアライズされた PyTorch モデルを C++ にロードするには、アプリケーションは LibTorch としても知られる、PyTorch C++ API に依拠しなければなりません。LibTorch ディストリビューションは共有ライブラリ、ヘッダファイルと CMake ビルド configuration ファイルのコレクションを包含します。CMake は LibTorch に依拠するための要件ではない一方で、それは推奨されるアプローチで将来に向けて上手くサポートされます。このチュートリアルについては、CMake と LibTorch を使用して最小限の C++ アプリケーションを構築していきます、これはシリアライズされた PyTorch モデルを単純にロードして実行します。
最小限の 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 <path_to_model>/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=<SliceBackward>)
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、ディスク上のシリアライズされたファイルそして – ループを閉じるために – C++ で実行可能な 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/
以上