PyTorchには,Python版だけではなく,C++版のAPIがリリースされています.
同じライブラリでも,コンパイラ言語「C++」とインタプリタ言語「Python」を実行した時にどちらがどのくらい速いかは,非常に気になるところです.
そこで今年も,**C++とPythonの速度比較**をやってみました!
前回(2019年度)の続きです.
1年前は,特定の環境下では,Pythonのほうが速い場合がありました.
そこで今年も,(TorchScriptを使わない場合の)訓練と推論の時間を計測していきます.
また,今回は,オートエンコーダだけではなく,さまざまなモデルで比較してみました!
実験で使ったソースコード(C++):https://github.com/koba-jon/pytorch_cpp
→つい最近,YOLOv2もアップロードしました!
実験の条件
実験環境
- OS: Ubuntu 20.04
- CUDA: 10.2
- cuDNN: 7.6.5
- CPU: Core i7-8700(12スレッド)
- GPU: GeForce GTX 1070
パッケージ
PyTorch公式:https://pytorch.org/
今回は,以下のパッケージをダウンロードしました.
Python版
- PyTorch Build: Stable (1.8.0)
- Your OS: Linux
- Package: Pip
- Language: Python
- Compute Platform: CUDA 10.2
C++版
- PyTorch Build: Stable (1.8.0)
- Your OS: Linux
- Package: LibTorch
- Language: C++/Java
- Compute Platform: CUDA 10.2
- Run this Command: Download here (cxx11 ABI)
モデル
全結合層のみ
- Autoencoder(1次元)
畳み込み層のみ
- Discriminator(DCGANより)
畳み込み層+転置畳み込み層
- Autoencoder(2次元)
- DCGAN
畳み込み層+プーリング層
- SegNet
畳み込み層+プーリング層+全結合層
- AlexNet
- ResNet(50層)
決定論的 or 非決定論的 (GPUオンリー)
GPUを使用する場合,cuDNNの挙動を変えることによって,速度が速くなったり遅くなったりします.
従って,この違いも速度比較に追加します.
ここでは,「再度プログラムを実行して全く同じ結果が得られる場合」は「決定論的」,そうでない場合は「非決定論的」とします.
Python版
- 決定論的
seed = 0
torch.manual_seed(seed)
torch.cuda.manual_seed(seed)
np.random.seed(seed)
random.seed(seed)
torch.backends.cudnn.deterministic = True # 速度が低下する代わりに決定論的
torch.backends.cudnn.benchmark = False # 速度が低下する代わりに決定論的
- 非決定論的
torch.backends.cudnn.deterministic = False # 非決定論的である代わりに高速化
torch.backends.cudnn.benchmark = True # 画像サイズが変化しない場合に高速化
C++版
- 決定論的
int seed = 0;
torch::manual_seed(seed);
torch::globalContext().setDeterministicCuDNN(true); // 速度が低下する代わりに決定論的
torch::globalContext().setBenchmarkCuDNN(false); // 速度が低下する代わりに決定論的
- 非決定論的
std::random_device rd;
torch::manual_seed(rd());
torch::globalContext().setDeterministicCuDNN(false); // 非決定論的である代わりに高速化
torch::globalContext().setBenchmarkCuDNN(true); // 画像サイズが変化しない場合に高速化
ライブラリの使用状況
使用したライブラリは,以下の通りです.
Python | C++ | |
---|---|---|
コマンドライン引数 | argparse | boost::program_options |
モデルの設計 | torch.nn | torch::nn |
前処理(transform) | torchvision.transforms | 自作(OpenCV使用) |
データセットの取得(datasets) | torchvision.datasets(Pillow使用) | 自作(OpenCV使用) |
データローダー(dataloader) | torch.utils.data.DataLoader | 自作(OpenMP使用) |
損失関数(loss) | torch.nn | torch::nn |
最適化手法(optimizer) | torch.optim | torch::optim |
誤差逆伝搬法(backward) | torch.Tensor.backward() | torch::Tensor::backward() |
プログレスバー | tqdm | 自作 |
※ちなみに,C++において,クラス分類以外でPyTorchをやる場合は,データのロードとビジュアライズ周りの多くは,おそらく自分で作ることになるので覚悟しておきましょう.
プログラミング言語間で統一させた項目
基本的には,Pythonで有るライブラリがC++には無いといったように,どうしようもない箇所以外は,ほとんど同じと思ってもらって構いません.
また,GitHubのプログラムから変えてないと思ってもらって結構です.
ソースコード(C++):https://github.com/koba-jon/pytorch_cpp
- データの次元(サイズ)
- データセットの種類(訓練・推論)
- 最適化手法
- モデルの構造
- モデルの初期化方法
- データのロード方法
- データセットのシャッフル方法
計測方法
Python: time
C++: std::chrono
- 訓練(Training)
1秒当たりの反復数(iteration数:データロード,順伝搬,逆伝搬,ビジュアライズなどを含む)を計測しました.
C++のプログラムを実行した場合,1[epoch]目が遅く,2[epoch]目から通常の速度になる場合があったため,(特に全結合層のみの場合)2[epoch]目以降の時間をもとに算出しました.
例えば,1[epoch]だけ処理するのに10[s]かかり,1[epoch]が1500[iteration]だったとします.
その場合は,1500[iteration] / 10[s] = 150[iteration/s]
となります.
- 推論(Inference)
1秒当たりのデータ処理数(順伝搬のみ)を計測しました.
これは,テストデータ全ての順伝搬の平均時間をもとに算出しました.
例えば,順伝搬1回分の処理に平均して0.005[s]かかったとします.
その場合は,1[forward] / 0.005[s] = 200[forward/s]
となります.
実験結果
全結合層のみ
データセット:正規分布データセット
入力データの形状:[N,D]=[64,300]
- Autoencoder(1次元)
訓練:1秒当たりの反復数
CPU(Core i7-8700) | GPU(GeForce GTX 1070) | ||
---|---|---|---|
決定論的 | 非決定論的 | ||
Python[iteration/s] | 86.83 | 97.69 | 97.69 |
C++[iteration/s] | 312.6 | 312.6 | 312.6 |
速度向上(Python→C++) | ×3.6 | ×3.2 | ×3.2 |
推論:1秒当たりのデータ処理数
CPU(Core i7-8700) | GPU(GeForce GTX 1070) | ||
---|---|---|---|
決定論的 | 非決定論的 | ||
Python[forward/s] | 3703.7 | 2272.73 | 2380.95 |
C++[forward/s] | 9090.91 | 4000 | 4166.67 |
速度向上(Python→C++) | ×2.45 | ×1.76 | ×1.75 |
畳み込み層のみ
データセット:MNIST
入力データの形状:[N,C,H,W]=[64,1,64,64]
- Discriminator(DCGANより)
訓練:1秒当たりの反復数
CPU(Core i7-8700) | GPU(GeForce GTX 1070) | ||
---|---|---|---|
決定論的 | 非決定論的 | ||
Python[iteration/s] | 5.24 | 27.59 | 39.08 |
C++[iteration/s] | 4.51 | 26.8 | 36.08 |
速度向上(Python→C++) | ×0.86 | ×0.97 | ×0.92 |
推論:1秒当たりのデータ処理数
CPU(Core i7-8700) | GPU(GeForce GTX 1070) | ||
---|---|---|---|
決定論的 | 非決定論的 | ||
Python[forward/s] | 222.72 | 2040.82 | 2000 |
C++[forward/s] | 204.92 | 3571.43 | 3225.81 |
速度向上(Python→C++) | ×0.92 | ×1.75 | ×1.61 |
畳み込み層+転置畳み込み層
データセット:CelebA
入力データの形状:[N,C,H,W]=[64,3,64,64]
- Autoencoder(2次元)
訓練:1秒当たりの反復数
CPU(Core i7-8700) | GPU(GeForce GTX 1070) | ||
---|---|---|---|
決定論的 | 非決定論的 | ||
Python[iteration/s] | 1.14 | 9.56 | 14.39 |
C++[iteration/s] | 1.05 | 9.16 | 13.44 |
速度向上(Python→C++) | ×0.92 | ×0.96 | ×0.93 |
推論:1秒当たりのデータ処理数
CPU(Core i7-8700) | GPU(GeForce GTX 1070) | ||
---|---|---|---|
決定論的 | 非決定論的 | ||
Python[forward/s] | 89.85 | 1136.36 | 1111.11 |
C++[forward/s] | 95.79 | 1851.85 | 1785.71 |
速度向上(Python→C++) | ×1.07 | ×1.63 | ×1.61 |
- DCGAN
訓練:1秒当たりの反復数
CPU(Core i7-8700) | GPU(GeForce GTX 1070) | ||
---|---|---|---|
決定論的 | 非決定論的 | ||
Python[iteration/s] | 0.94 | 6.43 | 9.53 |
C++[iteration/s] | 0.83 | 6.28 | 9.08 |
速度向上(Python→C++) | ×0.88 | ×0.98 | ×0.95 |
推論:1秒当たりのデータ処理数
CPU(Core i7-8700) | GPU(GeForce GTX 1070) | ||
---|---|---|---|
決定論的 | 非決定論的 | ||
Python[forward/s] | 92 | 1063.83 | 515.46 |
C++[forward/s] | 117.65 | 1515.15 | 598.8 |
速度向上(Python→C++) | ×1.28 | ×1.42 | ×1.16 |
畳み込み層+プーリング層
データセット:VOC2012
入力データの形状:[N,C,H,W]=[64,3,64,64]
- SegNet
訓練:1秒当たりの反復数
CPU(Core i7-8700) | GPU(GeForce GTX 1070) | ||
---|---|---|---|
決定論的 | 非決定論的 | ||
Python[iteration/s] | 0.51 | 4.56 | 6.83 |
C++[iteration/s] | 0.41 | 4.1 | 5.86 |
速度向上(Python→C++) | ×0.81 | ×0.9 | ×0.86 |
推論:1秒当たりのデータ処理数
CPU(Core i7-8700) | GPU(GeForce GTX 1070) | ||
---|---|---|---|
決定論的 | 非決定論的 | ||
Python[forward/s] | 47.55 | 401.61 | 331.13 |
C++[forward/s] | 53.02 | 735.29 | 490.2 |
速度向上(Python→C++) | ×1.12 | ×1.83 | ×1.48 |
畳み込み層+プーリング層+全結合層
データセット:MNIST
入力データの形状:[N,C,H,W]=[64,1,224,224]
- AlexNet
訓練:1秒当たりの反復数
CPU(Core i7-8700) | GPU(GeForce GTX 1070) | ||
---|---|---|---|
決定論的 | 非決定論的 | ||
Python[iteration/s] | 0.67 | 6.17 | 8.45 |
C++[iteration/s] | 0.56 | 5.27 | 6.9 |
速度向上(Python→C++) | ×0.84 | ×0.85 | ×0.82 |
推論:1秒当たりのデータ処理数
CPU(Core i7-8700) | GPU(GeForce GTX 1070) | ||
---|---|---|---|
決定論的 | 非決定論的 | ||
Python[forward/s] | 49.68 | 1219.51 | 1176.47 |
C++[forward/s] | 52.38 | 1785.71 | 1694.92 |
速度向上(Python→C++) | ×1.05 | ×1.46 | ×1.44 |
- ResNet(50層)
訓練:1秒当たりの反復数
CPU(Core i7-8700) | GPU(GeForce GTX 1070) | ||
---|---|---|---|
決定論的 | 非決定論的 | ||
Python[iteration/s] | 0.15 | 1.58 | 1.74 |
C++[iteration/s] | 0.13 | 1.52 | 1.68 |
速度向上(Python→C++) | ×0.85 | ×0.96 | ×0.97 |
推論:1秒当たりのデータ処理数
CPU(Core i7-8700) | GPU(GeForce GTX 1070) | ||
---|---|---|---|
決定論的 | 非決定論的 | ||
Python[forward/s] | 16.82 | 234.74 | 221.24 |
C++[forward/s] | 17.38 | 367.65 | 341.3 |
速度向上(Python→C++) | ×1.03 | ×1.57 | ×1.54 |
まとめ
「C++」と「Python」どっちが速いか?
訓練:全層「全結合層」の場合はC++の方が非常に速く,「畳み込み層」「転置畳み込み層」がある場合はPythonの方が少しだけ速い.
推論:全てのネットワークにおいて,C++の方がかなり速い.
- 全層「全結合層」の場合は,確実にC++の方が良い.
- それ以外を使う場合は,コーディング時間も考えて基本的にPythonで問題ない.
- できるなら,訓練だけ「Python」,推論だけ「C++」が良い.(ただ,この使い方ならTorchScriptを使った方が良さそう...)
新たに得られた知見
- 全層「全結合層」の推論の場合は,CPUだけ(GPUなし)の方が良い.(できればC++が良い)
- 「畳み込み層」「転置畳み込み層」がある場合は,GPUを使った方が圧倒的に良い.
- 実験結果の再現性を確保しなくていいなら,訓練は「非決定論的」,推論は「決定論的」でやるのが最も良い.(推論を「非決定論的」にしても速度の恩恵を受けないため)