syghの新フラグメント置き場

プログラミングTipsやコード断片の保管場所です。お絵描きもときどき載せます。

OpenCL/OpenGL/OpenCVのバイナリキャッシュ機能は使ってはいけない

OpenCL/OpenGLには当初、カーネルおよびシェーダープログラムに関してSPIR/SPIR-Vのような中間表現(バイトコード)規格が用意されておらず、それゆえオフラインコンパイルがサポートされていませんでしたが、コンパイル済みバイナリ(ベンダー依存)のキャッシュ機能はありました。

OpenCL 1.0:

OpenGL 4.1 or GL_ARB_get_program_binary:

また、OpenCV 2.xには、oclモジュールにおいて使用されるOpenCLカーネルのバイナリを、アプリケーションで使用する画像処理関数のカーネルごとに、初回呼び出し時に指定ディレクトリにファイル保存させることのできるキャッシュ機能が備わっていました。なお、保存されるファイル名にはOpenCLプラットフォーム名とデバイス名が含まれ、.clbの拡張子が付けられます。ただしワイド文字列のサポートはなく、したがってWindows上ではUnicodeがサポートされません*1

キャッシュ機能はOpenCV 3.0でいったんサポート外となった後、3.4にてOpenCL APIの薄いラッパーとして形を変えて復活したようです。

問題点

NVIDIA GeForceドライバーのとあるDirect3D 11リグレッション検証のために、グラフィックスドライバーのロールバック作業をしていたんですが、ついでにOpenCV 2.4.13を使ってOpenCV-CLの動作確認テストも実施したところ、OpenCLバイナリキャッシュの前方互換性がないことに気付きました。
具体的に言うと、例えば新しい388.71で出力したOpenCLカーネルのバイナリ*2を、古い353.90や364.72で読み込もうとすると、CL_INVALID_BINARYのエラーが発生します。OpenCV 2.4.13だと以下のようなエラーメッセージとなります。

XXX\sources\modules\ocl\src\cl_programcache.cpp:445: error: (-217) CL_INVALID_BINARY in function cv::ocl::ProgramFileCache::getOrBuildProgram

一応、古いカーネルを新しいドライバーで読み込むことはできる(後方互換性はある)ようですが、それが確実に保証されるのかどうか不明です。たぶん保証はされないでしょう。少なくとも前方互換性に関しては確保されていないことは確かです。おそらくOpenGLのシェーダープログラムバイナリに関しても似たような状況となっていることが予想されます。

これのどこが問題かというと、もしアプリケーションがカーネルバイナリをファイルとしてストレージにキャッシュ(保存)した後で、ユーザーがデバイスドライバーを変更すると、互換性がなくなって次回起動時にそのキャッシュが読み込めなくなり、アプリケーションが動作しなくなる、ということが懸念されるからです。
もしどうしてもバイナリキャッシュ機能を使いたい場合は、ドライバーの実装(バージョン番号)に紐づけた管理をするべきですが、少なくともOpenCV 2.xの実装はそうなっていません*3

なお、ドライバー変更以外でも、OpenCVのバージョンを変更すると、画像処理関数内部のカーネルが変わってOpenCLバイナリに互換性がなくなる、というケースがありえます。いっそOpenCV-CLのキャッシュ機能は使わないほうがよいでしょう。
少なくともPCにおけるAMD/NVIDIA環境に関しては、PCをシャットダウンするまで有効なオンメモリの組み込みキャッシュ機能がOpenGL/OpenCLドライバー側に備わっているはず*4なので、そちらに期待したほうがよいと思います。PC起動後の初回のコンパイル時間を我慢できれば、わざわざアプリケーション側でファイルとしてキャッシュする必要はありません。

ちなみにNVIDIA GeForceの場合、x86とx64とでOpenCLカーネルのバイナリ互換性がないらしいです。x86プロセスにてコンパイルしたバイナリをx64プロセスのドライバーに食わせようとすると、ドライバーがやはりCL_INVALID_BINARYのエラーを吐き、最悪の場合TDR後にドライバーがクラッシュすることもあるようです。普通に考えれば、ホストCPUアーキテクチャに依らずGPUコード側は同じはずなんですが、CUDA Unified Memory機能関連などでインターフェイス仕様の差異があるのかもしれません。Quadroは不明。
AMD FireProの場合は、少なくともCatalyst 15時点でx86/x64間の互換性がある模様です。Radeonは不明。

余談

ドライバー353.06だと、OpenCV 2.4.13のCV-CLで下記のエラーが発生します。別のバージョンのドライバーだと正常に動作するので、353.06のOpenCLドライバーにバグがある模様。

XXX\sources\modules\ocl\src\cl_operations.cpp:219: error: (-217) CL_MEM_OBJECT_ALLOCATION_FAILURE in function cv::ocl::openCLMemcpy2D

古いNVIDIAドライバーにはAPIが正常に動作しないバグがあるだけでなく、セキュリティ上の脆弱性も多々あるので、できるかぎり古いドライバーは使わないほうがよいです。
ところで先日、32bit版OS向けNVIDIAドライバーのサポートが打ち切られることが発表されましたが、現時点でWindows 7/8.1の32bit版は64bit版同様まだサポート期限が切れていないのに、今後32bit版において新しいハードウェアの対応がなされないのは企業ユーザーにとってかなり痛手になると思われます。32bit OS向けのセキュリティ対策は2019年1月まで続けられるそうですが、余命宣告を受けたも同然です。いずれにせよ、個人的にはどうでもいいんですけどね。

結論

やはりNVIDIA GeForceOpenCL実装は貧弱極まりないですね。また、それ以上にOpenCL/OpenGL仕様のいい加減さにあきれます。だからバイトコード規格を先に定義しろと(以下略)

*1:設計思想の古いOpenCVにとっては平常運転です。

*2:NVIDIAの場合、内部的にはCUDAで使われるPTXフォーマットになる模様です。

*3:クロスプラットフォームかつベンダー非依存な方法でドライバーのバージョン情報を取得する手段が標準化されていないこともあり、これはある意味仕方ないと思われます。

*4:OpenCL CもしくはGLSLのソースコード文字列に変更がなければ、一度コンパイルしたカーネルやシェーダープログラムのキャッシュが次回以降も使われるようです。