Amazon Ubuntu で TensorFlow のソースビルドが失敗する:メモリから切り分ける手順

この記事を最初に書いたのは2017年です。小さな Amazon Ubuntu インスタンスで TensorFlow のビルドに失敗し、当時は SWIG をインストールしたうえで、メモリ不足を疑い、Bazel の並列数を減らしてスワップを追加しました。この診断の要点は今も変わりません。コンパイラのプロセスが有用なエラーを残さず消える場合、マシンのメモリ不足によって強制終了された可能性があります。

一方、その周辺のコマンドは古くなりました。TensorFlow のビルドターゲット、Bazel のフラグ、対応する Python のバージョン、コンパイラ要件、GPU の設定方法はどれも変化しています。ここでは当時の経験を残しながら、現在推奨されるインストール方法と、負荷の大きいソースビルドを分けて整理します。

まず公式 wheel を使う

TensorFlow 本体を変更する、標準外の命令セットを有効にする、独自パッケージを作る、といった明確な理由がなければ公式 wheel を使います。短時間で再現でき、コンパイラや Bazel の互換性問題もほとんど避けられます。

対応している Linux 環境では、次のようにインストールできます。

sudo apt update
sudo apt install -y python3-pip python3-venv

python3 -m venv .venv
source .venv/bin/activate
python -m pip install --upgrade pip
python -m pip install tensorflow

cd /tmp
python -c 'import tensorflow as tf; print(tf.__version__)'

NVIDIA GPU を使う場合は、インストール前に TensorFlow の最新 pip ガイドと対応表を確認してください。現在の Linux 向けパッケージは次の方法です。

python -m pip install 'tensorflow[and-cuda]'
nvidia-smi
python -c 'import tensorflow as tf; print(tf.config.list_physical_devices("GPU"))'

pip が「一致するディストリビューションがない」と報告しても、すぐにソースビルドへ切り替えるべきではありません。まず CPU アーキテクチャと Python のバージョンが、TensorFlow の現在の対応条件に合っているか確認します。

本当にメモリ不足かを確かめる

ビルド方法を変える前に、環境情報を記録します。

uname -a
uname -m
python3 --version
free -h
swapon --show
df -h /

終了コード 137、単独の Killed、突然消えるコンパイラプロセスは、OOM(メモリ不足)による強制終了の兆候です。systemd を使う Ubuntu では、現在の起動セッションのカーネルログを確認します。

sudo journalctl -k -b | grep -Ei 'out of memory|oom|killed process' || true

ここを証拠で切り分けることが重要です。本物のコンパイルエラーならツールチェーンかソースを直す必要があり、OOM なら並列数を減らす、メモリを増やす、またはスワップを用意する必要があります。

TensorFlow のリリースとツールチェーンを固定する

適当なチェックアウトを、その場に入っている Bazel と Python でビルドしてはいけません。TensorFlow のタグを一つ選び、そのリリースについて公式ソースビルド対応表に記載されたバージョンを使います。TensorFlow は必要な Bazel バージョンを .bazelversion にも記録しており、Bazelisk なら自動的に選択できます。

git clone https://github.com/tensorflow/tensorflow.git
cd tensorflow

TF_TAG=v2.21.0  # あくまで例。環境が対応するリリースを選ぶ。
git checkout "$TF_TAG"
cat .bazelversion

クリーンな Python 環境を作り、選んだチェックアウトから設定処理を実行します。

sudo apt update
sudo apt install -y git python3-dev python3-pip python3-venv

python3 -m venv .venv
source .venv/bin/activate
python -m pip install --upgrade pip
./configure

そのタグが要求するコンパイラ、Bazel または Bazelisk、必要なら CUDA と cuDNN の正確なバージョンを入れます。公式手順では TensorFlow 2.13 以降の標準コンパイラは Clang ですが、検証済みのバージョンはリリースごとに異なります。

2017年の記事では、再試行前に SWIG をインストールしました。しかしこれは原因を証明した手順ではなく、当時の推測に基づく切り分けでした。現在、このビルド方法の公式前提条件に SWIG は含まれていません。

小さな CPU インスタンスでは控えめにビルドする

現在、CPU wheel の公式ターゲットは //tensorflow/tools/pip_package:wheel です。メモリが少ないマシンでは、ジョブを一つにし、Bazel の RAM 予算も控えめな値から始めます。

bazel build 
  --config=opt 
  --jobs=1 
  --local_ram_resources=2048 
  --verbose_failures 
  --repo_env=USE_PYWRAP_RULES=1 
  --repo_env=WHEEL_NAME=tensorflow_cpu 
  //tensorflow/tools/pip_package:wheel

--jobs=1 は同時実行する処理を減らします。--local_ram_resources=2048 はローカルアクションにおよそ 2 GB の RAM 予算を設定するための情報で、OS が強制するメモリ上限ではありません。この数値をそのままコピーせず、インスタンスに合わせて調整してください。

2017年のコマンドは --local_resources と古い //tensorflow/tools/pip_package:build_pip_package ターゲットを使っていました。これは歴史的な記録にすぎず、どちらも現在の公式コマンドではありません。

GPU wheel の現在のソースビルド手順では、--config=cuda--config=cuda_wheelWHEEL_NAME=tensorflow など、追加の CUDA 設定が必要です。異なる時代の CPU/GPU フラグを混ぜず、選んだリリースの公式ガイドに最初から最後まで従います。

スワップの追加は最後の手段にする

並列数を減らすだけで解決する場合もあります。それでもメモリ不足になるなら、大量のスワップを使ってコンパイルするより、インスタンスを大きくするほうが速く確実です。一時的なマシンや低予算の環境ではスワップファイルも役立ちますが、最初にホストの状態を確認します。

free -h
swapon --show
df -h /
test ! -e /swapfile || { echo '/swapfile already exists; stop and inspect it'; exit 1; }

Ubuntu の swapon ドキュメントによると、fallocate で作ったファイルは、穴(hole)を公開するファイルシステムでは拒否されることがあります。また Btrfs には専用の手順が必要です。一般的なファイルシステムでは、ゼロで埋めたファイルのほうが移植性の高い方法です。

sudo dd if=/dev/zero of=/swapfile bs=1MiB count=4096 status=progress
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile
swapon --show

既存のパスや正体を確認していないパスに対して mkswap を実行してはいけません。再起動後もこのスワップを使う場合は、/etc/fstab に次のエントリを一つだけ追加します。

/swapfile none swap sw 0 0

再起動する前に設定を検証します。

sudo findmnt --verify --verbose

ルートファイルシステムが Btrfs の場合、特殊な暗号化を使っている場合、またはイメージポリシーで管理されている場合は、そのプラットフォームが定めるスワップ手順を使ってください。

wheel をインストールして検証する

現在のビルドは wheel_house 以下に wheel を出力します。生成されたパッケージを有効な仮想環境へインストールし、ローカルのソースファイルがインストール済みパッケージを隠さないよう、ソースのチェックアウト外でテストします。

python -m pip install bazel-bin/tensorflow/tools/pip_package/wheel_house/*.whl

(
  cd /tmp
  python -c 'import tensorflow as tf; print(tf.__version__)'
)

症状からたどる実用的な切り分け表

症状最初に確認するもの次に行うこと
Killed、終了コード137、またはカーネルログの OOM 記録free -hswapon --showjournalctl -k--jobs を減らし、現実的な RAM 予算を設定する。インスタンスの拡張、または安全に設定したスワップを検討する
pip の No matching distribution foundPython バージョン、CPU アーキテクチャ、OS、TensorFlow の対応表対応する Python とプラットフォームを使う。ソースビルドは最初の解決策ではない
Bazel のバージョンエラー選んだタグの .bazelversion と公式ビルド対応表Bazelisk を使うか、検証済みの Bazel バージョンを正確に入れる
コンパイラまたはヘッダーのエラー--verbose_failures の全出力と、リリースで検証済みのコンパイラコンパイラと依存関係を選んだ TensorFlow タグに合わせる
CUDA が見つからない、または GPU の一覧が空ドライバの認識、nvidia-smi、現在の CUDA/cuDNN 要件一つのリリースについて公式 GPU 手順を最初から最後まで実施する
チェックアウト内でしか import できない、または動作が不自然現在のディレクトリと python -m pip show tensorflow/tmp などソースツリー外からテストする

最初の失敗から学んだこと

2017年の観察で役立ったのは「SWIG を入れる」ことではありません。小さなクラウドインスタンスでのソースビルド失敗は、パッケージやフラグを変える前に分類しなければならない、という点です。カーネルがコンパイラを強制終了したかを確認し、ビルドツールチェーンを一つの TensorFlow リリースに合わせ、カスタムビルドに明確な利点がなければ公式 wheel を使うのが安全です。

公式資料

当時の経緯については、TensorFlow の初期の Issue にあるコンパイル時のメモリ不足ソースビルドの失敗の議論からも、小さなマシンでよく起きていた問題だったことが分かります。

Leave a Reply