メインコンテンツへスキップ
このページでは、SLURM、EC2 Spot、Google Cloud のプリエンプト可能 VM などのプリエンプト可能な環境で sweeps を確実に実行できるように、W&B Sweeps がシステムシグナルとプロセス終了コードをどのように処理するかを詳しく説明します。これらのセクションでは、キーボードから run を適切に中断する方法を説明し、run をキューに入れ直す挙動を理解して予測するのに役立つ詳細を示します。プリエンプト時に runs がどのようにキューに入れ直されるかについて詳しくは、プリエンプト可能な Sweeps run を再開するを参照してください。

終了ステータスとシグナル

W&B は、トレーニングプロセスの終了ステータスをもとに、run をキューに入れ直すかどうかと、run の状態をどのように記録するかを判断します。 終了コードの取り決め:
  • 終了コード 0: run は正常に完了したと見なされ、キューに入れ直されません。
  • 0 以外の終了コード: run は失敗またはプリエンプトされたものとして扱われます。mark_preempting() を使用すると、W&B は run をキューに入れ直し、別の agent (または再起動後の同じ agent) が再開できるようにします。
これは、プロセスがシグナルハンドラ、例外、または明示的な sys.exit() 呼び出しによって終了した場合のいずれにも当てはまります。プリエンプト可能な環境やクラスター 環境では、この取り決めを理解し、それに基づいて動作することが極めて重要です。 プロセスが 捕捉可能な シグナル によって終了する場合、ハンドラ内で必要な処理を実行できます。run をキューに入れ直したい場合は wandb.run.mark_preempting() を呼び出し、クリーンアップ (たとえば checkpoint の保存) を行ったうえで、0 以外のコードで終了します。一般的な慣例は、シグナルによる終了時に sys.exit(128 + signum) を使用することです。W&B はその終了コードを記録し、同じ キューに入れ直しのルール が適用されます。プロセスが SIGKILL によってオペレーティングシステムのカーネルに kill された場合、プロセスは終了フックを実行できないため、最終的な summary は書き込まれず、run は crashed または killed として表示されることがあります。それでも、agent は次の run を開始します。

応答のない run とサーバー側のタイムアウト

run が長時間にわたって終了せず、新しいメトリクスも送信しない場合 (およそ 5 分程度) 、W&B Server はその run を crashed としてマークします。これは、トレーニングプロセスがハングしたり、メトリクスのログ送信が止まったり、正常な終了処理を行わないまま停止されたりした場合 (たとえば SIGKILL の後) によく発生します。一定の間隔でメトリクスをログするか、明示的な終了コードで終了すると、実際に起きたことと run の状態を一致させやすくなります。

捕捉可能なシグナルとプリエンプション

トレーニングスクリプトにカスタムのシグナルハンドラーを登録できます。捕捉可能なシグナルを受信すると、ハンドラーが実行されます。すでに W&B に送信されたメトリクスは保持され、agent はプロセスの終了を検知して次の run を開始します。 ベストプラクティス:
  • ハンドラーは早い段階で登録してください (たとえば、メインのトレーニングループに入る前) 。
  • ハンドラー内では、プリエンプション後に run をキューに入れ直す場合は wandb.run.mark_preempting() を呼び出し、クリーンアップ (たとえば checkpoint の保存) を行ってから、非ゼロのコードで終了してください。
次の例では、SIGUSR1 (クラスター で一般的なプリエンプションシグナル) と SIGTERM のハンドラーを登録します。SIGINT は対話的な用途 (たとえば、ターミナルからの手動キャンセル) のために使えるよう残しています。ハンドラーは wandb.run.mark_preempting() を呼び出し、128 + signum を使って終了します:
import signal
import sys
import wandb


def signal_handler(signum, frame):
    if wandb.run is not None:
        # オプション: モデル チェックポイントの保存、バッファのフラッシュなど。
        print(f"Preempted with signal: {signal.Signals(signum).name}.")
        wandb.run.mark_preempting()
    sys.exit(128 + signum)


def train():
    signal.signal(signal.SIGUSR1, signal_handler)
    signal.signal(signal.SIGTERM, signal_handler)

    with wandb.init() as run:
        config = wandb.config
        for epoch in range(100):
            # トレーニング step; 必要に応じて wandb.log(...) を呼び出す
            pass


if __name__ == "__main__":
    train()

SIGKILL (捕捉不可)

SIGKILL は捕捉も無視もできません。プロセスは即座に終了するため、ハンドラーや atexit コールバックを実行する余地はありません。W&B はその run の最終 summary を書き込めません。agent は復旧して引き続き sweep を継続しますが、その run のデータは不完全になります。SIGKILL の使用は最後の手段にとどめ、正常にシャットダウンする必要がある場合は SIGTERM または SIGINT を優先してください。

agent から子プロセスへのシグナル転送

wandb agent CLI を使用すると、agent はトレーニングスクリプトを子プロセスとして実行します。agent を中断しても (たとえば Ctrl+C を押した場合や、スケジューラがジョブに SIGTERM を送信した場合) 、既定では 子プロセス (トレーニングプロセス) にそのシグナルは渡されません。そのため、トレーニングスクリプトはハンドラを実行したり、mark_preempting() を呼び出したりできません。これは GitHub #3667 で説明されています。 子プロセスが正常にシャットダウンし、ハンドラ内で wandb.run.mark_preempting() を呼び出せるようにするには、CLI agent を --forward-signals 付きで実行します。
wandb agent --forward-signals entity/project/sweep_ID
Python API の wandb.agent() では、シグナル転送はサポートされていません。この経路ではトレーニング関数は独立した子プロセスではなくスレッド内で実行されるため、同じ転送動作は適用されません。 転送を有効にした CLI agent が SIGINT または SIGTERM を受信すると、そのシグナルを子プロセスに中継します。これによりトレーニングスクリプトのハンドラーが実行され、必要に応じて wandb.run.mark_preempting() と、非ゼロの終了コードを指定した wandb.finish() を呼び出し、非ゼロコードで終了できます。agent プロセスで Ctrl+C を 2 回押すと、デフォルトでは agent は SIGTERM を受信します。--forward-signals を使用すると、SIGINT を子プロセスに転送してハンドラーを実行できます。 詳しくは、wandb agent CLI リファレンスを参照してください。

SLURM のようなプリエンプト可能なクラスター

プリエンプト時には、トレーニングプロセスがシグナルを受け取り、run をプリエンプト中としてマークしたうえで、run がキューに入れ直されるよう非ゼロの終了コードで終了する必要があります。そうすると、新しい agent (またはジョブがキューに入れ直された後の同じ agent) が run を再開できます。 トレーニングプロセスがシグナルを受け取るようにしてください。
  1. スケジューラがagentにシグナルを送る場合: wandb agent --forward-signals を指定してagentを実行してください。これにより、スケジューラ (またはユーザー) がagentにシグナルを送信したとき、そのシグナルが子プロセスに転送されます。すると子プロセスのハンドラで wandb.run.mark_preempting()、非ゼロコードを指定した wandb.finish(exit_code=...)、および sys.exit(128 + signum) (または別の非ゼロ終了コード) を呼び出せます。
  2. スケジューラが起動スクリプトにシグナルを送る場合 (agentに直接送らない場合) : 起動スクリプトからプリエンプトシグナルをトレーニングプロセスに直接送るようにしてください。たとえば、トレーニングスクリプトが自分のプロセス ID をファイルに書き込み、起動スクリプトがクラスターのシグナル (たとえば SIGUSR1) を捕捉して kill -SIGUSR1 $(cat $PID_FILE) を実行すれば、トレーニングプロセスのハンドラが実行されます。
トレーニングスクリプト内: クラスターで使用されるシグナル (たとえば SIGTERM または SIGUSR1) のハンドラを登録してください。ハンドラ内では、run がアクティブなら wandb.run.mark_preempting() を呼び出し、その後、run がキューに入れ直されるよう非ゼロの終了コードで run を終了し、sys.exit(128 + signum) (または別の非ゼロコード) を実行してください。run がいつキューに入れ直されるか、およびそれが mark_preempting() とどう関係するかについては、プリエンプト可能な Sweeps の run を再開するを参照してください。 Sweep の状態: agentを起動する前に wandb sweep entity/project/sweep_ID --resume を実行し、sweep を再開モードにして、キューに入れ直された run が割り当てられるようにしてください。 複数agentの協調: 多数のagentが同時に実行される場合 (SLURM の array job など) 、同じプリエンプト済み run の取得を競合することがあります。これは既知の制限事項です。この問題を回避しやすくするため、agentの起動タイミングをずらすか、lock などの外部協調メカニズムを使用してください。

wandb sweep --cancel

sweep をキャンセルするには、OS シグナルではなく W&B API を使用します。wandb sweep --cancel entity/project/sweep_ID のようなコマンドを実行してください。サーバーが agent に終了を指示し、その後 agent は実行中の子プロセスを終了して停止します。キャンセルが有効になるまで、短い遅延 (agent の API ポーリング間隔と同程度) が生じることがあります。 キャンセルでは、run に SIGKILL が送られます。子プロセスは、ユーザー定義のシグナルハンドラーを実行する機会がありません。これは、Sweeps UI の Cancel コントロールを使用した場合も同様です。sweep 全体を停止してキャンセル済みとしてマークしたい場合は、--cancel を使用してください。現在の run を正常終了させるには、run に捕捉可能なシグナルを送ります (または、CLI agent で --forward-signals を使用し、agent にシグナルを送ります) 。sweep を正常に完了させるには、--cancel ではなく wandb sweep --stop を使用してください。 一時停止、再開、停止、キャンセルのオプションについては、Manage sweeps を参照してください。

agent の終了と run の終了

agent プロセス (子のトレーニングプロセスではなく) にシグナルを送ると、子プロセスは孤立したまま実行を続け、agent だけが終了することがあります。孤立したプロセスはターミナルへの出力を続けることがあり、Enter キーを押すまでシェルに新しいプロンプトが表示されない場合があります。 CLI agent で --forward-signals を使用しない限り、agent を停止しても子のトレーニングプロセスが停止するとは限りません。 agent が終了したことを確認するには、プロンプトが表示されるかどうかで判断するのではなく、ps -p <agent_pid>pgrep -f "wandb agent" のような OS コマンドを使用してください。

参照: mark_preempting() と最終的な run の状態

以下の表は、mark_preempting()いつ呼び出すかと、プロセスがどのように終了するかに応じて、run の状態がどうなるかをまとめたものです。これは、トレーニングプログラムをサブプロセスとして wandb agent CLI を使用することを前提としています。
シナリオmark_preempting() なしシグナルハンドラが mark_preempting() を呼び出して非ゼロで終了init() の直後に常に mark_preempting() を呼び出す
run が終了コード 0 で正常に完了するFINISHEDFINISHEDFINISHED
run が非ゼロの終了コードで失敗するFAILEDFAILEDPREEMPTED
run が SIGKILL を受信する約 5 分後に CRASHED約 5 分後に CRASHED (捕捉不可)約 5 分後に PREEMPTED
run が SIGINT を受信するKILLEDPREEMPTED (SIGINT ハンドラがある場合)PREEMPTED
run が別のシグナル (たとえば SIGTERMSIGUSR1) を受信する約 5 分後に CRASHEDPREEMPTED (対応するハンドラがある場合)約 5 分後に PREEMPTED
mark_preempting() をシグナルハンドラ内でしか呼び出さない場合は、SIGKILL のようにハンドラがまったく実行されないケースをカバーできません。 wandb.init() の直後に常に mark_preempting() を呼び出すと、あらゆる失敗がプリエンプションとして扱われる可能性があり、bug や不適切な設定が原因の場合でも、run が繰り返しキューに入れ直されることがあります。 プリエンプションシグナルが明確に定義されている環境では、一般的には mark_preempting() を呼び出して非ゼロで終了するシグナルハンドラを使用し、init() の直後に無条件で呼び出すことはしません。