🐍

ValueError: numpy.ndarray size changed

3 min read

TL;DR

  • NumPy v1.20.0 でABIの変更がありバイナリ非互換に
    • NumPyそのものではなく、Cythonなどを利用してNumPyのC APIを利用してコンパイルされているライブラリでバイナリ非互換の問題に遭遇する
  • エンドユーザーの対処としては、該当のライブラリを自分でコンパイルすること
    • pip install --no-binary (ライブラリ名)
  • ライブラリ開発者の対処としては、v1.19.5以前の NumPy でコンパイルしてPyPIに登録すること

0. 発見した経緯

開発している強化学習向けライブラリcpprbKaggleのコンペで利用している時に遭遇した。

ノートブック
import cpprb

ValueError: numpy.ndarray size changed, may indicate binary incompatibility. Expected 88 from C header, got 80 from PyObject

しかも、一旦実行に成功していたノートブックを再度実行させようとした際に発生したので、ノートブック内でインストールしているバージョンの問題だろうと思われた。

1. 問題の根源

NumPy v1.20.0 でABIの変更があり、バイナリ非互換となった。(リリースノートRPおよびレビュー)

Size of np.ndarray and np.void_ changed

PR作成者は、エンドユーザーにはあまり影響は出ないはずだし、影響が出そうなCythonユーザー(開発者)としても実行速度が速くなって喜ぶはずだとの意見のようです。
レビューアーは、バイナリ非互換について懸念していましたが、Pythonの公式実装のCPythonもC APIについてはバイナリ互換性を保証していないみたいなので仕方ないかと納得したようです。

cpprbはCythonを利用しています。開発時には、CI上で綺麗な環境でビルドしているため、NumPy v1.20.0でビルドして、PyPIに登録されていました。

https://gitlab.com/ymd_h/cpprb/-/jobs/1004626476

一方、TensorFlow が古いバージョンのNumPyを要求するため、Kaggleやその他の実際に利用する環境では古いNumPyが入っており、バイナリ非互換で実行時エラーとなっていました。

2. 対処

(脱線)エンドユーザー側での対処

この記事の主題からは外れますが、エラーメッセージの検索結果からやってくるエンドユーザー向けの対処を書いておきます。
該当のライブラリが異なるNumPyのバージョンとコンパイルされているのが問題のため、各自の手元でソースコードからコンパイルしてください。

PyPIにソースコードもアップロードされていれば、以下のコマンドでビルドできる可能性が高いです。(どんなコンパイラやライブラリが必要になるかは該当のライブラリの開発元の情報をあたってください。)

pip install --no-binary (ライブラリ名)

2.1 情報収集

実際には、1章で書いた原因は、はじめから知っていたわけではなく、関連するエラーメッセージの検索など調査からはじめました。(NumPy 1.20.0という新しいバージョンが出たばかりということは一応知っていました。)

調査の際には、 GiLab の issue に書くほどではないことを、Zennのスクラップに書いてトラブルシューティングを進めました。
(ちょこちょこ書き足していけるので、こういうトラブルシューティングに非常に便利ですよね。運が良ければ、誰かからアドバイスもらえるかもしれませんし。)

https://zenn.dev/ymd_h/scraps/fd2bc283cfa221

調査の流れはここに書くには細かすぎるので、気になる人はスクラップを見ていただければと思いますが、利用環境の調査・エラーメッセージの検索による類似事例の調査・関連する変更点の確認をしています。

2.2 手元での試験

バイナリの非互換性を調べるために、手元でDockerを利用して試験をしました。
(プロジェクトのソースコードのマウントおよび、pipのwheel (バイナリ) をキャッシュしておくディレクトリをマウントしてシェルに入ります。)

docker run -it -v (適当なディレクトリ):/root/.cache/pip -v (開発プロジェクトのパス):/cpprb  python:3.8 bash
  1. OK: NumPy v1.19.5 でビルドして、NumPy v1.20.0で利用
    • pip install numpy==1.19.5 && pip install /cpprb && pip install -U numpy==1.20.0 && python -c "import cpprb"
  2. NG: NumPy v1.20.0 でビルドして、NumPy v1.19.5で利用
    • pip install numpy==1.20.0 && pip install /cpprb && pip install -U numpy==1.19.5 && python -c "import cpprb"

(実際の試験時にはワンライナーコマンドではなく、1つずつ実行しましたが。)

※ 利用といっても import でエラーが出るかしかチェックできていないので、よりしっかり使っていくと問題があるかもしれません。

先に書いたように、TensorFlowが古いバージョンのNumPyを要求している以上、cpprbの大多数のユーザーはNumPy v1.20.0 を利用していないだろうと推測できるので、NumPy v1.19.5 でビルドしたものを PyPIに登録することに。

2.3 CI上の対処

事前にNumPyをインストールしてから、cpprbをビルドすることにした。尚 (私がつい失敗してしまったのですが、) == はそのまま指定できますが、 < はクオートでくくらないと、シェルのファイルリダイレクト入力と解釈されてエラーになります。(自戒)

pip install 'numpy<1.20.0'
pip install (ライブラリのパス)

https://gitlab.com/ymd_h/cpprb/-/commit/587bcbb86c39e940fe086d72704536dca0579397

2.4 アナウンス

最後に最近作ってみた GitHub Discussions に新しくAnnouncementというカテゴリーを作成して、エンドユーザーの対処法を告知した。
まずは、最新のcpprbに更新して、それでも駄目ならソースからビルドしてくださいと。

https://github.com/ymd-h/cpprb/discussions/3

Discussionsも過疎っているからもっと使ってほしいですが、そもそも絶対的なユーザー数が少な(ry