C++にTorchScriptモデルをロードする

10848 ワード

このチュートリアルはPyTorch 1.2と一緒に使用できるように更新されました
名前の通り、PyTorchの主なインタフェースはPythonプログラミング言語です.Pythonは、ダイナミックで反復しやすい多くのシーンに適しており、優先言語であるが、Pythonのこれらの属性は、多くの場合不利である.後者が一般的に適用される環境の1つは、生産-低遅延と厳格な導入を要求することです.本番シーンでは、Java、Rust、Goなどの別の言語にC++のみをバインドしても、よく選択される言語です.次のセクションでは、PyTorchが提供する既存のPythonモデルから、Pythonに依存することなく、C++から完全にロードおよび実行可能なシーケンス化表現形式へのパスについて概説します.
手順1:PyTorchモデルをTorchスクリプトに変換する
PyTorchモデルPythonからC++への旅はTorch Scriptによって開始され、Torch ScriptはPyTorchモデルの表現形式であり、Torch Scriptコンパイラによって理解され、コンパイルされ、シーケンス化されることができる.vanilla「eager」APIを使用して作成された既存のPyTorchモデルから開始する場合は、まずモデルをTorchスクリプトに変換する必要があります.最も一般的な場合(以下で説明します)は、わずかな手間がかかります.すでにTorchスクリプトモジュールがある場合は、このチュートリアルの次のセクションにジャンプできます.
PyTorchモデルをTorchスクリプトに変換する方法は2つあります.1つ目は追跡と呼ばれ、モデルの構造をサンプル入力を用いて評価し、モデルに入力された流量を記録するメカニズムであり、モデルの構造をキャプチャする.これは制御フローを有限に使用するモデルに適している.2つ目の方法は、Torch ScriptコンパイラがTorch Script言語によって加えられた制約に基づいてモデルコードを直接解析およびコンパイルできることを示すために、モデルに明示的なコメントを追加することである.
ヒント:この2つの方法に関する完全なドキュメントと、使用方法の詳細については、公式Torchスクリプトリファレンスを参照してください.
方法1:トレースによるTorchスクリプトへの変換
PyTorchモデルをトレースによってTorchスクリプトに変換するには、モデルのインスタンスおよびサンプル入力をtorch.jit.trace関数に渡す必要があります.これにより、モジュールのtorch.jit.ScriptModuleメソッドにモデル評価痕跡が埋め込まれるforwardオブジェクトが生成されます.
import torch
import torchvision

#         .
model = torchvision.models.resnet18()

#           forward()       。
example = torch.rand(1, 3, 224, 224)

#   `torch.jit.trace `       `torch.jit.ScriptModule`
traced_script_module = torch.jit.trace(model, example)

追跡された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=)

方法2:コメントからTorchスクリプトへの変換
場合によっては、たとえば、モデルが特定の形式の制御フローを使用する場合、Torchスクリプトに直接モデルを記述し、それに応じてモデルを注釈する必要がある場合があります.たとえば、次の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

このモジュールの順方向メソッドは、入力された制御フローに依存するため、追跡には適していません.逆に、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)

まだサポートされていないPython機能を使用しているため、nn.Moduleからいくつかの方法を除外する必要がある場合は、TorchScriptを使用してコメントできます.@torch.jit.ignoremy_moduleの例であり、シーケンス化することができる.
手順2:スクリプトモジュールをファイルにシーケンス化する
ScriptModuleができたら(PyTorchモデルをトレースまたはアノテーションすることによって)ファイルにシーケンス化できます.後で、Pythonに依存することなく、C++を使用してこのファイルからモジュールをロードして実行できます.トレース例に以前に表示されていたScriptModuleモデルをシーケンス化するとします.このシーケンス化を実行するには、モジュールでsaveを呼び出してファイル名を渡すだけです.
traced_script_module.save("traced_resnet_model.pt")

これにより、作業ディレクトリにResNet18ファイルが生成されます.また、traced_resnet_model.ptをシーケンス化したい場合は、my_moduleを呼び出します.Python領域を正式に離れ、C++領域にまたがる準備ができています.
手順3:C++にスクリプトモジュールをロードする
C++にシーケンス化されたPyTorchモデルをロードするには、アプリケーションはPyTorch C++APIに依存する必要があります(LibTorchとも呼ばれる).LibTorchリリース版には、共有ライブラリ、ヘッダファイル、CMake構築プロファイルのセットが含まれています.CMakeはLibTorchの要件に依存するものではありませんが、推奨される方法であり、将来的にはよくサポートされます.このチュートリアルでは、CMakeとLibTorchを使用して最小のC++アプリケーションを構築します.このアプリケーションは簡単にロードして実行できますシーケンス化されたPyTorchモデル.
最小のC++アプリケーション
ロードモジュールのコードについて説明しましょう.以下は既に行われています.
include  // One-stop header.

#include 
#include 

int main(int argc, const char* argv[]) {
  if (argc != 2) {
    std::cerr << "usage: example-app 
"; return -1; } torch::jit::script::Module module; try { // : torch::jit::load(). module = torch::jit::load(argv[1]); } catch (const c10::Error& e) { std::cerr << "error loading the model
"; return -1; } std::cout << "ok
"; }
my_module.save("my_module_model.pt")ヘッダーには、サンプルを実行するために必要なLibTorchライブラリのすべての関連項目が含まれています.私たちのアプリケーションは、シーケンス化されたPyTorch ScriptModuleのファイルパスを唯一のコマンドラインパラメータとして受け入れ、関数を使用してモジュールを逆シーケンス化し続け、このファイルパスを入力として使用します.戻ると、torch::jit::load()オブジェクトが受信されます.後で実行方法について説明します.
LibTorchおよび構築アプリケーションに依存
上記のコードをTorch::jit::script::Moduleというファイルに格納するとします.最小example-app.cppは簡単に見えるかもしれません.
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 11)

サンプルアプリケーションを作成する最後のことは、LibTorchリリースです.PyTorchのWebサイトのダウンロードページから最新の安定したバージョンをいつでも入手できます.最新のアーカイブをダウンロードして解凍する場合は、次のディレクトリ構造を持つフォルダに格納します.
libtorch/
  bin/
  include/
  lib/
  share/
  • lib/フォルダには、リンクする必要がある共有ライブラリが含まれています
  • include/フォルダには、プログラムに含める必要があるヘッダファイルが含まれています
  • share/フォルダには、上記の単純CMakeLists.txtコマンドを有効にするために必要なCMake構成が含まれています.

  • ヒント;Windowsでは、デバッグとリリースはABIと互換性がありません.デバッグモードでプロジェクトを構築する場合は、LibTorchのデバッグバージョンを使用してみてください.
    最後のステップは、アプリケーションの構築です.このため、サンプルディレクトリのレイアウトは次のようになります.
    example-app/
      CMakeLists.txt
      example-app.cpp
    

    次のコマンドを実行して、find_package(Torch)フォルダからアプリケーションを構築できます.
    mkdir build
    cd build
    cmake -DCMAKE_PREFIX_PATH=/path/to/libtorch ..
    make
    
    example-app/は、解凍された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
    

    サンプル・アプリケーション・バイナリ・ファイルへの追跡ResNet 18モデル/path/to/libtorchへのパスを以前に作成した場合は、奨励として友好的な「ok」を使用する必要があります.traced_resnet_model.ptを使用してこの例を実行しようとすると、入力したシェイプが互換性がないことを示すエラーメッセージが表示されます.my_module_model.pt4 Dではなく1 Dが必要です.
    root@4b5a67132e81:/example-app/build# ./example-app /traced_resnet_model.pt
    ok
    

    手順4:C++でスクリプトモジュールを実行する
    C++でシーケンス化されたResNet 18のロードに成功した後、コードを数行実行するだけでいいです.これらの行をC++アプリケーションのmy_module_model.pt関数に追加します.
    //       
    std::vector<:jit::ivalue> inputs;
    inputs.push_back(torch::ones({1, 3, 224, 224}));
    
    //              
    at::Tensor output = module.forward(inputs).toTensor();
    std::cout << output.slice(/*dim=*/1, /*start=*/0, /*end=*/5) << '
    ';

    最初の2行には、モデルの入力が設定されています.main()のベクトルを作成します(タイプtype-erasedの値torch::jit::IValueメソッドを受け入れて返します)を追加し、単一の入力を追加します.入力テンソルを作成するには、C++APIのScript::Moduleと同等のtorch::ones()を使用します.次に、torch.onesscript::Moduleメソッドを実行し、作成した入力ベクトルを渡します.見返りとして、forwardを呼び出すことによって、新しいIValueが得られます.|テンソルに変換します.
    ヒント:torch::onesやPyTorch C++APIなどの機能の詳細については、ドキュメント、URLを参照してください.https://pytorch.org/cppdocs. PyTorch C++APIはPython APIとほぼ同じ機能パリティを提供し、Pythonのようにテンソルをさらに操作して処理することができます.
    最後の行では、出力の最初の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=)
    

    よくマッチしているようですね.
    ヒント:モデルをGPUメモリに移動するには、modelを作成します.to(at::kCUDA);.tensor.to(at::kCUDA)を呼び出すことで、モデルの入力もCUDAメモリにあることを確認し、CUDAメモリに新しいテンソルを返します.
    手順5:ヘルプの取得とAPIの探索
    このチュートリアルでは、PyTorchモデルのPythonからC++へのパスを大まかに理解することができます.このチュートリアルで説明した概念を使用すると、vanilla、eagerのPyTorchモデル、Pythonのコンパイル済みtoTensor()、ディスク上のシーケンス化ファイル、および–ループの終了–実行可能スクリプト:C++のモジュールに進むことができます.
    もちろん、私たちが紹介していない概念はたくさんあります.たとえば、C++またはCUDAで実装するカスタム演算子拡張ScriptModuleを使用して、純粋なC++本番環境にロードされたScriptModuleでカスタム演算子を実行したい場合があります.良いニュースは:これは可能で、そしてとても良い支持を得ました!これで、このフォルダの例を参照できます.すぐにチュートリアルを提供します.通常、次のリンクが役立ちます.
  • Torch Scriptリファレンス:https://pytorch.org/docs/master/jit.html
  • PyTorch C++APIドキュメント:https://pytorch.org/cppdocs/
  • PyTorch Python APIドキュメント:https://pytorch.org/docs/

  • いつものように、何か問題や疑問があれば、私たちのフォーラムやGitHub issuesを使用して連絡することができます.
    磐創AI技術ブログ資源まとめステーション:http://docs.panchuang.net/PyTorch公式中国語教程ステーション:http://pytorch.panchuang.net/OpenCV中国語公式文書:http://woshicver.com/