LoginSignup
25
20

More than 3 years have passed since last update.

PyTorch C++ VS Python(2020年度版)

Last updated at Posted at 2021-03-20

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版

  • 決定論的
deterministic.py
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     # 速度が低下する代わりに決定論的
  • 非決定論的
non_deterministic.py
torch.backends.cudnn.deterministic = False  # 非決定論的である代わりに高速化
torch.backends.cudnn.benchmark = True       # 画像サイズが変化しない場合に高速化

C++版

  • 決定論的
deterministic.cpp
int seed = 0;
torch::manual_seed(seed);
torch::globalContext().setDeterministicCuDNN(true);  // 速度が低下する代わりに決定論的
torch::globalContext().setBenchmarkCuDNN(false);  // 速度が低下する代わりに決定論的
  • 非決定論的
non_deterministic.cpp
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

p1.png

畳み込み層のみ

データセット: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

p2.png

畳み込み層+転置畳み込み層

データセット: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

p3.png

  • 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

p4.png

畳み込み層+プーリング層

データセット: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

p5.png

畳み込み層+プーリング層+全結合層

データセット: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

p6.png

  • 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

p7.png

まとめ

「C++」と「Python」どっちが速いか?

訓練:全層「全結合層」の場合はC++の方が非常に速く,「畳み込み層」「転置畳み込み層」がある場合はPythonの方が少しだけ速い.
推論:全てのネットワークにおいて,C++の方がかなり速い.

  • 全層「全結合層」の場合は,確実にC++の方が良い.
  • それ以外を使う場合は,コーディング時間も考えて基本的にPythonで問題ない.
  • できるなら,訓練だけ「Python」,推論だけ「C++」が良い.(ただ,この使い方ならTorchScriptを使った方が良さそう...)

新たに得られた知見

  • 全層「全結合層」の推論の場合は,CPUだけ(GPUなし)の方が良い.(できればC++が良い)
  • 「畳み込み層」「転置畳み込み層」がある場合は,GPUを使った方が圧倒的に良い.
  • 実験結果の再現性を確保しなくていいなら,訓練は「非決定論的」,推論は「決定論的」でやるのが最も良い.(推論を「非決定論的」にしても速度の恩恵を受けないため)

参考URL

25
20
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
25
20