少ないリソースを酷使する

低レイヤーとレトロPC(PC98,MSX)が好きな情報学生

とっても古いPython,OpenCV,TensorFlow環境を構築する時の苦悩過程

この記事は「岩手県立大学 Advent Calendar 2023」の25日目(後出し)です。

qiita.com

ことのはじまり

私はGitHubを見ていて「あ、このリポジトリ動かしたいな」と思った時、まずはそのリポジトリが動く環境のDocker image構築から始まります。
動いた環境を人に共有しやすく、なるべくベース環境はきれいに保てるので。
さて、いつも通り環境を構築しようとしましたがリポジトリには以下のような要件が提示されていました。

github.com

  1. Ubuntu 16.04
  2. Python 2.7
  3. OpenCV 2.4.13 compiled with GPU support
  4. boost_python 1.65.0
  5. TensorFlow
  6. Sonnet

全体的に要求環境が古いですね。
というのも最終コミットが2019年なので納得です。

環境構築の困難さ

そのままモダンな環境で動くのか

要求環境は古いですが、別にUbuntu 20.04 + Python3.x + OpenCV 4.x ...でそのまま動いてくれるなら何も気にすることはありません。
が、今回の場合以下の問題が発生します。

  1. 記法が違う部分があるため、Python3系に合うようにPythonスクリプトを書き換える必要あり
  2. OpenCVAPI互換がなく、書き換えが必要な部分多々あり

正直、今回のコード規模ぐらいであれば全部書き換えても大したことないとは思うのですが、 もっと大規模なコードで同じ問題に陥っていた場合にはモダナイゼーション(使い方あってる?)には以下のリスクが伴います。

  1. 過去に自分が作ったシステムでない限り、モダナイゼーションした後に起こったバグは元々のソフトウェアのバグか自分が修正を加えたせいで出たバグか切り分けるのが難しい
  2. 関数のデフォルトアルゴリズムが変わっていたりすると計算結果が合わずつらい(オプションで過去のアルゴリズムが選択できることがほとんどだが)
  3. システムの規模によるが単純に書き換えが手間、ChatGPTとかにモダンにしてって言えばそれなりにぱぱっとできるかもしれないがそれでも手間だし、計算結果が想定通りじゃないと更に頭を抱える

ということで、元々の環境を再現して動かせるならなるべくその方がいい。とは思います。

今回の障壁

今回面倒なのは以下の3つの条件です。

  1. Python 2.7
  2. OpenCV 2.4.13 compiled with GPU support
  3. TensorFlow

まず、これは今回の例に限ったことではないのですが「マシンについている物理GPU」「OpenCV」「TensorFlow」全員が納得するCUDA環境を整えるのは非常に面倒です。 また、Python 2.7自体は用意するのはなんてこと無いのですが、Python 2.7と連携できるTensorFlowやその他パッケージのバージョンを用意する必要があります。 今回開発マシンには「NVIDIA GeForce RTX 3070」がついているのでこのアーキテクチャ対応も意識する必要があります。

TensorFlow(GPU版)の問題

TensorFlowで学習、推論を回すならGPUアクセラレーションが使えるならぜひ使いたいところです。
CPU版でやっていてはとてもじゃないけど遅すぎて開発効率が悪いことがありますからね。
Python2.7と連携できるtensorflow-gpuversion 1.15.xが最後です。
version 1.15.xに対応するCUDA環境はCUDA:10.0 cuDNN:7になります。
がしかし、「NVIDIA GeForce RTX 3070」は最低でもCUDA:11.1が必要です。
よって、Python2.7の使用を続ける場合この時点でtensorflowのGPUアクセラレーションは諦めるしかなくなり詰みます。

古いOpenCV(with CUDA)の問題

ではOpenCVはどうでしょう?
OpenCV with CUDA をビルドするには一例ではありますが以下のようなビルドオプションが必要です。

RUN cmake -D CMAKE_BUILD_TYPE=RELEASE \
    -D CMAKE_INSTALL_PREFIX=/usr/local \
    -D WITH_CUDA=ON \
    -D WITH_CUDNN=ON \
    -D OPENCV_DNN_CUDA=ON \
    -D CUDA_ARCH_BIN='8.6' \
    -D CUDA_ARCH_PTX='8.6' \
    -D ENABLE_FAST_MATH=1 \
    -D CUDA_FAST_MATH=1 \
    -D WITH_CUBLAS=1 \
    -D WITH_TBB=ON \
    ..

重要なのはCUDA_ARCH_BINCUDA_ARCH_PTXの2つで、ここに書く数字はPCに搭載されているGPUによって最適な数字が異なります。
CUDAのバージョンによって変わる数字ではないので注意が必要です。
以下のリストから自分のGPUの名前を探し、「Compute Capability」に書いてある数字を参照します。

CUDA GPUs - Compute Capability | NVIDIA Developer

よって、「NVIDIA GeForce RTX 3070」では「8.6」となります。
もちろん、CUDA_ARCH_BIN='8.6'でビルドするにはCUDA:11.1以降が必要となりますが(参考)、 そうなると今回要求されているOpenCV 2.4.13サポート範囲外となります。
このような「Compute Capability」とOpenCVのバージョンの適応関係は以下の記事によくまとめられています。

qiita.com

「じゃあもうめんどいんで、GPUアクセラレーションなしのOpenCV 2.4.13インストールするんでとりあえず動いて」と思ってしまいますが、 ここで面倒なのはOpenCVGPUアクセラレーションを使ったコードからの脱却方法です。
TensorFlowなどではほぼコードの書き換えなしにCPU,GPUどちらのリソースで計算するかは気軽に切り替えられるのですが、 OpenCVGPUアクセラレーションを使用する/しないはコードの記述に依存するので、GPUアクセラレーションから脱却するには該当範囲をすべてCPU対応に書き直す必要があります。(単純な書き換えではありますが)
最初からCPUでもGPUでも動くように環境によって分岐するように優しく書かれているコードだとありがたいですが、そうでない場合&コードの規模が大きい場合少々手間です。

おとしどころ

ここまでの制約を整理すると

要素 一言 要求されるCUDA範囲
物理GPU 世代によってCUDAのMin値は決まる 11.1 < X
TensorFlow Python2.7で使えるtensorflow-gpuの最大は1.15.x X < 10.0
OpenCV 2系は古すぎ、GPUから脱却するのもやや面倒 3.0 < 9.2

ここからやるべきことは以下のように整理されます。

  1. ベースは CUDA 11.1
  2. Pythonを3系にして、tensorflow-gpuを使えるようにする
  3. OpenCVのバージョンを上げるためにコードを書き換える

2はオプショナルで別にやらなくてもCPU計算で動くには動くので1と3をやることにします。

最終的な解決

まずDockerのベースimageをUbuntu16.04 + CUDA11.1 + cuDNN8のものにします。
DockerHubのnvidia/cudaからは古いものは消されていくのですが、 nvidia/cudaにない古いものもnvcr.io/nvidia/cudaからとってくることができます。

catalog.ngc.nvidia.com

OpenCVはなんとなく4.5.2にしてみました。
2系から4系にあげたため、各種APIの仕様が変わりそのままでは動きません。
動くように"頑張って"cppやpythonを書き換えてエラーを消していきます。

ここでやっと動きます。

まとめ

特になんの参考になるかわかりませんが、私が普段更新の古いリポジトリを動かすときに考えていることを書き出してみました。
結果的に「Python3にする」とか「OpenCVのバージョンを上げる」とかめんどくさがらずに書き換えるしか無いこともありますが、なるべくそのまま動かすためにどう情報を参照すべきかはまとまったと思います。
これを読んだみなさんも"動かない😡"と大量にissueが立てられてるリポジトリが動く環境を提供してみると、救われる人類もいるかも知れません。

おまけ

その1

なんか各種環境のtensorflow-gpuのwhlリンクだけを大量にまとめてるサイトがあります。
Links for tensorflow-gpu

その2

TensorFlowは古いバージョンだとpipのパッケージ名によって、

  • tensorflow・・・CPU版
  • tensorflow-gpu・・・GPU

とわかれていましたが最近のバージョンはtensorflowを入れるだけでどっちにも対応します。

その3

OpenCVをCUDA付きでビルドする際にサポートされる範囲 #OpenCV - Qiita
この記事だとOpenCV 2.4.13.7のCUDAの対応は最大9.2ですが、 CUDA 9系 + OpenCV 2.4.13でビルドしようとするとリンクエラーで素直にはビルドできないので注意です。cmakeを書き換える必要があります。
ここらへんで議論されてます。
stackoverflow.com