Transformers documentation
デバッグ
デバッグ
分散学習の問題のデバッグは、通常、数値的な問題、通信の失敗、実行時エラー、ビルドエラーのいずれかのカテゴリに分類されます。
アンダーフローとオーバーフローの検出
アンダーフローとオーバーフローは、アクティベーションや重みがinfまたはnanになった場合、あるいはloss=NaNになった場合に発生します。これらを検出するには、TrainingArguments.debug()でDebugUnderflowOverflowモジュールを有効にするか、インポートして独自のトレーニングループに追加してください。
from transformers import TrainingArguments
args = TrainingArguments(
debug="underflow_overflow",
...
)from transformers.debug_utils import DebugUnderflowOverflow
debug_overflow = DebugUnderflowOverflow(model)DebugUnderflowOverflow は、モデルにフックを挿入し、各 forward 呼び出し後に、入力および出力の変数と対応するモデルの重みをテストします。アクティベーションまたは重みの少なくとも1つの要素で inf または nan が検出された場合、このモジュールは以下のようなレポートを出力します。
以下の例は、google/mt5-small による fp16 混合精度トレーニングを示しています。
Detected inf/nan during batch_number=0
Last 21 forward frames:
abs min abs max metadata
encoder.block.1.layer.1.DenseReluDense.dropout Dropout
0.00e+00 2.57e+02 input[0]
0.00e+00 2.85e+02 output
[...]
encoder.block.2.layer.0 T5LayerSelfAttention
6.78e-04 3.15e+03 input[0]
2.65e-04 3.42e+03 output[0]
None output[1]
2.25e-01 1.00e+04 output[2]
encoder.block.2.layer.1.layer_norm T5LayerNorm
8.69e-02 4.18e-01 weight
2.65e-04 3.42e+03 input[0]
1.79e-06 4.65e+00 output
encoder.block.2.layer.1.DenseReluDense.wi_0 Linear
2.17e-07 4.50e+00 weight
1.79e-06 4.65e+00 input[0]
2.68e-06 3.70e+01 output
encoder.block.2.layer.1.DenseReluDense.wi_1 Linear
8.08e-07 2.66e+01 weight
1.79e-06 4.65e+00 input[0]
1.27e-04 2.37e+02 output
encoder.block.2.layer.1.DenseReluDense.dropout Dropout
0.00e+00 8.76e+03 input[0]
0.00e+00 9.74e+03 output
encoder.block.2.layer.1.DenseReluDense.wo Linear
1.01e-06 6.44e+00 weight
0.00e+00 9.74e+03 input[0]
3.18e-04 6.27e+04 output
encoder.block.2.layer.1.DenseReluDense T5DenseGatedGeluDense
1.79e-06 4.65e+00 input[0]
3.18e-04 6.27e+04 output
encoder.block.2.layer.1.dropout Dropout
3.18e-04 6.27e+04 input[0]
0.00e+00 inf output最初の行は、エラーが発生したバッチ番号を示しています。この場合、バッチ0で発生しました。
各フレームは、そのフレームが報告するモジュールを説明しています。例えば、以下のフレームは、エンコーダーの2番目のブロックの1番目のレイヤーにあるレイヤーノームであるencoder.block.2.layer.1.layer_normについて報告しています。フォワード呼び出しはT5LayerNormへのものです。
encoder.block.2.layer.1.layer_norm T5LayerNorm 8.69e-02 4.18e-01 weight 2.65e-04 3.42e+03 input[0] 1.79e-06 4.65e+00 output
最後のフレームは、DenseReluDense クラス内の dropout 属性を呼び出す Dropout.forward 関数について報告しています。オーバーフロー(inf)は、エンコーダーの2番目のブロックで最初のバッチ時に発生しました。最大の入力要素は 6.27e+04 でした。
encoder.block.2.layer.1.DenseReluDense T5DenseGatedGeluDense
1.79e-06 4.65e+00 input[0]
3.18e-04 6.27e+04 output
encoder.block.2.layer.1.dropout Dropout
3.18e-04 6.27e+04 input[0]
0.00e+00 inf outputT5DenseGatedGeluDense.forward の出力アクティベーションは最大 6.27e+04 に達しており、これは fp16 の最大値である 6.4e+04 に近い値です。次のステップで、Dropout が一部の要素をゼロにした後に重みを再正規化するため、最大値が 6.4e+04 を超え、オーバーフローが発生します。
エラーが発生している箇所が分かったところで、modeling_t5.py のモデリングコードを調査してください。
class T5DenseGatedGeluDense(nn.Module):
def __init__(self, config):
super().__init__()
self.wi_0 = nn.Linear(config.d_model, config.d_ff, bias=False)
self.wi_1 = nn.Linear(config.d_model, config.d_ff, bias=False)
self.wo = nn.Linear(config.d_ff, config.d_model, bias=False)
self.dropout = nn.Dropout(config.dropout_rate)
self.gelu_act = ACT2FN["gelu_new"]
def forward(self, hidden_states):
hidden_gelu = self.gelu_act(self.wi_0(hidden_states))
hidden_linear = self.wi_1(hidden_states)
hidden_states = hidden_gelu * hidden_linear
hidden_states = self.dropout(hidden_states)
hidden_states = self.wo(hidden_states)
return hidden_states対処法の一つは、値が大きくなりすぎる数ステップ前に fp32 に切り替えることです。これにより、乗算や加算の際に数値がオーバーフローすることを防げます。別の選択肢としては、混合精度学習(amp)を一時的に無効化する方法があります。
import torch
def forward(self, hidden_states):
device_type = hidden_states.device.type
if torch.is_autocast_enabled(device_type):
with torch.amp.autocast(device_type, enabled=False):
return self._forward(hidden_states)
else:
return self._forward(hidden_states)このレポートは、フルフレームの入力と出力のみをカバーします。forward 関数内の中間値を分析するには、各 forward 呼び出しの後に detect_overflow を追加し、forwarded_states 内の inf または nan を追跡してください。
from transformers.debug_utils import detect_overflow
class T5LayerFF(nn.Module):
[...]
def forward(self, hidden_states):
forwarded_states = self.layer_norm(hidden_states)
detect_overflow(forwarded_states, "after layer_norm")
forwarded_states = self.DenseReluDense(forwarded_states)
detect_overflow(forwarded_states, "after DenseReluDense")
return hidden_states + self.dropout(forwarded_states)DebugUnderflowOverflowが出力するフレーム数を設定します。
from transformers.debug_utils import DebugUnderflowOverflow
debug_overflow = DebugUnderflowOverflow(model, max_frames_to_save=100)バッチトレース
DebugUnderflowOverflowは、アンダーフローとオーバーフローの検出を無効にした状態で、各バッチ内の絶対的な最小値と最大値を追跡することもできます。これにより、モデル内で値がどのように分岐し始めるのかを特定するのに役立ちます。
以下の例では、バッチ1とバッチ3をトレースします(バッチは0始まりのインデックスです)。
debug_overflow = DebugUnderflowOverflow(model, trace_batch_nums=[1, 3]) *** Starting batch number=1 ***
abs min abs max metadata
shared Embedding
1.01e-06 7.92e+02 weight
0.00e+00 2.47e+04 input[0]
5.36e-05 7.92e+02 output
[...]
decoder.dropout Dropout
1.60e-07 2.27e+01 input[0]
0.00e+00 2.52e+01 output
decoder T5Stack
not a tensor output
lm_head Linear
1.01e-06 7.92e+02 weight
0.00e+00 1.11e+00 input[0]
6.06e-02 8.39e+01 output
T5ForConditionalGeneration
not a tensor output
*** Starting batch number=3 ***
abs min abs max metadata
shared Embedding
1.01e-06 7.92e+02 weight
0.00e+00 2.78e+04 input[0]
5.36e-05 7.92e+02 output
[...]DebugUnderflowOverflow は多くのフレームを報告するため、値が分岐する箇所を特定しやすくなります。問題がバッチ 150 付近にあることがわかっている場合は、バッチ 149 と 150 にトレースを絞り込み、数値がどこから異なるようになるかを比較してください。
特定のバッチ番号、例えばバッチ3でトレースを停止することもできます。
debug_overflow = DebugUnderflowOverflow(model, trace_batch_nums=[1, 3], abort_after_batch_num=3)コミュニケーション
分散学習はプロセス間およびノード間の通信を必要としますが、これはエラーの一般的な原因となります。
以下のスクリプトをダウンロードしてネットワークの問題を診断し、実行してGPU間の通信をテストしてください。以下のコマンドは2つのGPUをテストします。システムに合わせて--nproc_per_nodeと--nnodesを調整してください。
wget https://raw.githubusercontent.com/huggingface/transformers/main/scripts/distributed/torch-distributed-gpu-test.py python -m torch.distributed.run --nproc_per_node 2 --nnodes 1 torch-distributed-gpu-test.py
スクリプトは、両方のGPUが正常に通信し、メモリを割り当てた場合にOKを出力します。詳細およびSLURM環境で実行する手順については、診断スクリプトを参照してください。
詳細な NCCL デバッグ出力を取得するには、NCCL_DEBUG=INFO を設定してください。
NCCL_DEBUG=INFO python -m torch.distributed.run --nproc_per_node 2 --nnodes 1 torch-distributed-gpu-test.py
DeepSpeed
エラーが発生した場合は、まず原因がDeepSpeedかどうかを確認してください。DeepSpeedなしでセットアップを再試行し、エラーが継続する場合は問題を報告してください。Transformersとの統合に関連しない問題については、DeepSpeedのリポジトリで問題を起票してください。
Transformersの統合に関連する問題については、以下の情報を含めてください。
完全な DeepSpeed 設定ファイル。
Trainerのコマンドライン引数、または Trainer のセットアップを自分でスクリプトで構築する場合は TrainingArguments を使用します(TrainingArguments には無関係なエントリが多数含まれているため、そのまま丸ごとダンプしないでください)。
これらのコマンドの出力。
python -c 'import torch; print(f"torch: {torch.__version__}")' python -c 'import transformers; print(f"transformers: {transformers.__version__}")' python -c 'import deepspeed; print(f"deepspeed: {deepspeed.__version__}")'問題を再現するための Google Colab ノートブックへのリンク。
問題を再現するための、標準的な(カスタムでない)データセットまたは既存の例。
起動時にプロセスが終了しました
DeepSpeed プロセスが起動中にトレースバックなしで強制終了した場合、プログラムは利用可能または許可されている CPU メモリ量を超えてメモリを割り当てようとしていた可能性があります。いずれの場合も、OS カーネルがプロセスを終了します。
設定ファイルに offload_optimizer、offload_param、または両方が CPU へのオフロード用に設定されているか確認してください。
NVMeとZeRO-3が設定されている場合は、代わりにNVMeへのオフロードを試してください。まず、推定してモデルのメモリ要件を確認してください。
NaN損失
NaN loss は、モデルが bf16 で事前学習され、その後 fp16 で使用される場合に頻繁に発生します(TPU で学習されたモデルで特に一般的です)。ハードウェアがサポートしている場合(TPU、Ampere GPU 以降)は、fp32 または bf16 を使用してください。
fp16でもオーバーフローが発生する可能性があります。設定ファイルが以下のような場合、ログにオーバーフローエラーが表示される場合があります。
{
"fp16": {
"enabled": "auto",
"loss_scale": 0,
"loss_scale_window": 1000,
"initial_scale_power": 16,
"hysteresis": 2,
"min_loss_scale": 1
}
}以下の OVERFLOW! エラーは、DeepSpeed のロススケーラーがロスオーバーフローを克服するためのスケーリング係数を見つけられなかったことを意味します。initial_scale_power の値を大きくしてみてください(32 が通常動作します)。
0%| | 0/189 [00:00<?, ?it/s] [deepscale] OVERFLOW! Rank 0 Skipping step. Attempted loss scale: 262144, reducing to 262144 1%|▌ | 1/189 [00:00<01:26, 2.17it/s] [deepscale] OVERFLOW! Rank 0 Skipping step. Attempted loss scale: 262144, reducing to 131072.0 1%|█▏ [...] [deepscale] OVERFLOW! Rank 0 Skipping step. Attempted loss scale: 1, reducing to 1 14%|████████████████▌ | 27/189 [00:14<01:13, 2.21it/s] [deepscale] OVERFLOW! Rank 0 Skipping step. Attempted loss scale: 1, reducing to 1 15%|█████████████████▏ | 28/189 [00:14<01:13, 2.18it/s] [deepscale] OVERFLOW! Rank 0 Skipping step. Attempted loss scale: 1, reducing to 1 15%|█████████████████▊ | 29/189 [00:15<01:13, 2.18it/s] [deepscale] OVERFLOW! Rank 0 Skipping step. Attempted loss scale: 1, reducing to 1 [...]
DeepSpeed CUDA
DeepSpeed は CUDA C++ コードをコンパイルしますが、これは CUDA が必要な PyTorch 拡張機能のビルドエラーの一般的な原因です。これらのエラーは、システムに CUDA がどのようにインストールされているかに依存します。
pip install deepspeed
その他のインストールに関する問題が発生した場合は、DeepSpeedチームにissueを起票してください。
非同一のツールキット
PyTorchには独自のCUDAツールキットが同梱されていますが、DeepSpeedにはシステム全体に同一のCUDAバージョンがインストールされている必要があります。Python環境でcudatoolkit==10.2を指定してPyTorchをインストールした場合は、システム全体にCUDA 10.2もインストールする必要があります。
正確な場所はシステムによって異なりますが、Unix システムでは/usr/local/cuda-10.2が最も一般的なパスです。CUDA のセットアップが完了し、PATHに追加されたら、以下のコマンドでインストール場所を確認できます。
which nvcc複数のツールキット
システムには、複数の CUDA ツールキットがインストールされている場合があります。
/usr/local/cuda-10.2 /usr/local/cuda-11.0
パッケージのインストーラーは通常、パスを最後にインストールされたバージョンに設定します。ビルドが正しい CUDA バージョンを見つけられずに失敗する場合は、PATH と LD_LIBRARY_PATH を正しいパスを指すように設定してください。
まず、これらの環境変数を確認してください。
echo $PATH
echo $LD_LIBRARY_PATHPATH は実行可能ファイルの場所を列挙します。LD_LIBRARY_PATH は共有ライブラリの場所を列挙します。先頭の項目が優先され、: で複数の項目を区切ります。優先順位を上げるには、正しい CUDA パスを先頭に追加してください。
# adjust the version and full path if needed
export PATH=/usr/local/cuda-10.2/bin:$PATH
export LD_LIBRARY_PATH=/usr/local/cuda-10.2/lib64:$LD_LIBRARY_PATH割り当てられたディレクトリが存在することを確認してください。lib64 サブディレクトリには、libcudart.so のような CUDA .so オブジェクトが含まれています。実際のファイル名を確認し、必要に応じて更新してください。
旧バージョン
古い CUDA バージョンでは、古いコンパイラバージョンが必要になる場合があります。例えば、CUDA が gcc-7 を要求しているのに、システムに gcc-9 しかない場合、ビルドは失敗します。必要な古いコンパイラをインストールし、CUDA のビルドシステムが見つかるようにシンボリックリンクを作成してください。
# adjust the path to your system
sudo ln -s /usr/bin/gcc-7 /usr/local/cuda-10.2/bin/gcc
sudo ln -s /usr/bin/g++-7 /usr/local/cuda-10.2/bin/g++事前ビルド
DeepSpeed のインストールや実行時ビルドに引き続き問題がある場合は、まず DeepSpeed モジュールを事前ビルドしてください。ローカルビルドには、以下のコマンドを実行します。
git clone https://github.com/deepspeedai/DeepSpeed/
cd DeepSpeed
rm -rf build
TORCH_CUDA_ARCH_LIST="8.6" DS_BUILD_CPU_ADAM=1 DS_BUILD_UTILS=1 pip install . \
--global-option="build_ext" --global-option="-j8" --no-cache -v \
--disable-pip-version-check 2>&1 | tee build.logNVMeオフロードを使用するには、ビルドコマンドに
DS_BUILD_AIO=1を追加してください。また、libaio-devパッケージをシステム全体にインストールしていることを確認してください。
次に、TORCH_CUDA_ARCH_LIST に GPU アーキテクチャを設定します。NVIDIA GPU とそのアーキテクチャの完全なリストは、CUDA GPUs ページ に掲載されています。お使いのアーキテクチャに対応する PyTorch のバージョンを確認するには、以下のコマンドを実行してください。
python -c "import torch; print(torch.cuda.get_arch_list())"以下のコマンドで、GPUのアーキテクチャを確認できます。
CUDA_VISIBLE_DEVICES=0 python -c "import torch; print(torch.cuda.get_device_capability())"GPU 0 のアーキテクチャを確認するには、以下のコマンドを実行します。出力には、GPU アーキテクチャを構成する major と minor の値が表示されます。以下の例では、アーキテクチャが 8.6 であることが示されています。
CUDA_VISIBLE_DEVICES=0 python -c "import torch; \
print(torch.cuda.get_device_properties(torch.device('cuda')))
"_CudaDeviceProperties(name='GeForce RTX 3090', major=8, minor=6, total_memory=24268MB, multi_processor_count=82)"8, 6 の結果の場合、TORCH_CUDA_ARCH_LIST="8.6" を設定します。異なるアーキテクチャを持つ複数の GPU を使用する場合、TORCH_CUDA_ARCH_LIST="6.1;8.6" のように列挙します。
TORCH_CUDA_ARCH_LISTを省略して、ビルドプログラムにGPUアーキテクチャを自動検出させることもできますが、ターゲットマシンの実際のGPUと一致しない場合があります。アーキテクチャを明示的に設定する方が確実です。
同じ構成の複数マシンでのトレーニングを行う場合は、バイナリホイールをビルドします。
git clone https://github.com/deepspeedai/DeepSpeed/
cd DeepSpeed
rm -rf build
TORCH_CUDA_ARCH_LIST="8.6" DS_BUILD_CPU_ADAM=1 DS_BUILD_UTILS=1 \
python setup.py build_ext -j8 bdist_wheelこれにより、dist/deepspeed-0.3.13+8cd046f-cp38-cp38-linux_x86_64.whl のようなバイナリホイールが生成されます。ローカルまたは別のマシンにインストールしてください。
pip install deepspeed-0.3.13+8cd046f-cp38-cp38-linux_x86_64.whl