【実験】Google Colabでよくある「切断された😢」を少しでも減らすために

GoogleColab(以下Colab)を無料枠で使っていると、セッションが切断されてしまうことがあります。稼働残り時間には余裕があることで、セッションの切断に気が付かずに、長時間実行する機械学習のトレーニングや、大量データの処理が切断されると、非常に困りますよね。

特にショックが大きいのはColabで時間制限が厳しいGPUリソースを使用している場合でしょうか。自分はWhisperを多用しているので、音声認識ではGPUリソースを使用するのですが、長時間の音声ファイルの音声認識にはかなり時間がかかり、print()処理までに時間がかかることもあり、たまに認識中にセッションが切断して😢😢😢という状況多々発生します。

そこでColabのセッションを維持するための方法について、いくつかの実用的な実験してみました。これはあまりおすすめ出来ない手法もありますが、背に腹は代えられないこともあります🙄

Colabの予期せぬセッション切れを予防・緩和するための実験であり、完全な解決策ではありません。状況によっては効果が限定的な場合もありますが、セッション切れのリスクを減らすための参考になればと思います。

Colabのセッションが切断される原因?

Colab は、一定時間アクティビティがないと「アイドル状態」とみなしてセッションを切断されるようです。以下のような条件で切断されるようですが、このあたりの正確な動き体感なものです🙇

  • ブラウザが非アクティブになる(最小化、他のタブを開くなど)
  • 実行中のスクリプトが一定時間出力をしない
  • CPU や GPU の使用率が低く、アイドルと判断される
  • Google のリソース管理ポリシーによる切断(無料版では時間制限がされることがあるようです)

情報を調べてみると以下のようになっているそうです。

無料プラン

  • アイドル状態が90分を超えると切断(アイドル状態でなくても切れるときあるんだけどなあ😫)。
  • 最大セッション時間は12時間。

Colab Pro/Pro+ - アイドル状態の制限が緩和され、最大24時間までセッションを保持可能。

ただし、これらの制限は状況やリソースの使用状況によって変動する可能性があり、必ずしも最大時間までセッションが維持されることが保証されているわけではないようです。自分も1日実験してみましたが、アイドル状態では60分でセッションは切れます(処理が動いていない場合にきれるのはやむなしです)。

これを回避するために、Colab 上で「アクティブな状態」を維持する方法を試行してみます。

【実験1】 print()を定期的に実行する(最も手軽ですが…)

Colab出力がない状態が続くと「アイドル」とみなされる ため、定期的に print() を実行することで、セッションを維持できる可能性があります。実はこれ手法を使用しても切断されたことがあります😫

import time
import sys

while True:
    print("セッション維持中...", flush=True)
    sys.stdout.flush()  # バッファをクリアして確実に出力
    time.sleep(60*10)  # 10分ごとに出力

ポイント

  • print() を実行し、Colab に「処理が続いている」と認識させる。
  • flush=Truesys.stdout.flush() を使って即座に出力を反映させる。
  • sleep(60*10) で 10分ごと に処理を実行し、適度に Colab にアクティビティを送る。

基本的にColabのセッションが維持されることが多いですが、問題は処理内にprint()を入れる余地がない場合だと思います。


【実験2】multiprocessingでバックグラウンド処理を並列実行する

Colab基本的には1つのセルしか実行できない仕様ですが、multiprocessingを使うことで、セッション維持用のプロセスをバックグラウンドで実行しながら、別の処理を進めることもできます。それで、アクティブな処理を入れるというものになります。

以下の手法では標準出力に文字列を出すのですが、別のセルではこの動きがみえないので、もう少し工夫が必要です。

multiprocessing初期版

import multiprocessing
import time

def keep_colab_alive():
    while True:
        print("セッション維持中...", flush=True)
        time.sleep(60*10)

# バックグラウンドでプロセスを起動
p = multiprocessing.Process(target=keep_colab_alive)
p.start()

print("セッション維持プロセスを開始しました!")

もし、動作の様子を確認するのであれば、状態の表示部分を標準エラー出力にして、さらにリダイレクトをColab上のファイルに出力するのがお勧めてです。

multiprocessing(処理確認改良版)

import multiprocessing
import time
import sys

def log_error(message, log_file="/content/error_log.txt"):
    """エラーメッセージをログファイルに出力する関数"""
    with open(log_file, "a") as f:  # "a" モードで追記
        sys.stderr = f
        print(message, file=sys.stderr)

def keep_colab_alive():
    while True:
        log_error("セッション維持中...")
        time.sleep(60*10)
        log_error("Sleep処理完了")

# バックグラウンドでプロセスを起動
p = multiprocessing.Process(target=keep_colab_alive)
p.start()
print("セッション維持プロセスを開始しました!")

ちなみに動作するセルが変わっているので、そのままでは動作が継続しています。もし終了させるには以下を実行して止めまることもできます。

プロセスの停止処理

p.terminate()
print("セッション維持プロセスを停止しました。")

【実験3】 subprocess()で独立したPythonプロセスを並列実行する

subprocess.Popen() を使うと、Colabで独立した Python プロセスをバックグラウンドで実行 できるというものです。

subprocess初期版

import subprocess

# Pythonプロセスをバックグラウンドで起動
p = subprocess.Popen(["python", "-c", """
import time
while True:
    print('セッション維持中...', flush=True)
    time.sleep(60*10)
"""])

print("バックグラウンドプロセスを開始しました!")

こちらも、動作の様子を確認するのであれば、状態の表示部分を標準エラー出力にして、さらにリダイレクトをColab上のファイルに出力するのがお勧めです。

subprocess(処理確認改良版)

import subprocess

# Pythonプロセスをバックグラウンドで起動
p = subprocess.Popen(["python", "-c", """
import time
import sys
def log_error(message, log_file="/content/error_log.txt"):
    with open(log_file, "a") as f:  # "a" モードで追記
        sys.stderr = f
        print(message, file=sys.stderr)
while True:
    log_error("セッション維持中...")
    time.sleep(60*10)
    log_error("Sleep処理完了")
"""])

print("バックグラウンドプロセスを開始しました!")

ちなみに動作するセルが変わっているので、そのままでは動作が継続しています。もし終了させるには以下を実行して止めまることもできます。

プロセスの停止処理

p.terminate()
print("バックグラウンドプロセスを停止しました。")

別のプロセスとして起動していますが、コードの記述部分がエラー訂正などが聞きにくいので若干面倒かなと感じます。


multiprocess()とsubprocess()の補足

Pythonmultiprocess()subprocess()は、異なる目的で使用される並列処理です。

multiprocess()は、Pythonプログラム内での並列処理を実現します。CPU負荷の高い計算処理やデータ処理を複数のプロセスなどを並列実行できます。QueuePipe、共有メモリなどを使ってプロセス間でデータを共有することも可能です。

一方、subprocess()は外部コマンドやプログラムを実行します。シェルコマンドの実行や他のプログラムの起動、その実行結果の取得などができます。標準入出力を介して外部プロセスとの通信も可能です。

Pythonプログラム内での並列処理が必要な場合はmultiprocess()を、外部コマンドやプログラムを実行する必要がある場合はsubprocess()を使うのが適切です。


【実験4】 nohup を使ってBashでバックグラウンド実行

Pythonの処理以外の方法として、Bash (Colab なので冒頭に"!"つけてください) を使って バックグラウンドで実行し続けるスクリプト を設定することもできるのではないかと考えました。nohup(no hang up)は、ターミナルを閉じた後もプロセスを継続実行させるためのツールです。&と組み合わせることで、バックグラウンドでの実行が可能になります。

!nohup bash -c 'while true; do echo "セッション維持中"; sleep 600; done' &

プロセスの停止方法

!pkill -f "bash"

【実験5】threadingを使用する方法

Pythonのもつスレッドの機能を使用してバックグラウンド処理でセッション接続を維持する方法、先程までの方法ではプロセス的な考え方でしたが、今回はスレッドを用いた方法となります。こちらも後続のセルの実行はできます。

結果は問題なく動作しました。

参考

zenn.dev

import time
import threading

def keep_alive():
    while True:
        print("Keeping session alive...")
        time.sleep(600)  # 10分間隔で出力

# バックグラウンドスレッドを起動
thread = threading.Thread(target=keep_alive, daemon=True)  # daemon=True でバックグラウンド実行
thread.start()

print("バックグラウンドでセッションを維持中...")

現状はこのやり方が良さそうですね。

【実験6】ブラウザのデバッグコンソールにスクリプトを埋め込む

このやり方は、Colabに依存せずにブラウザの機能によって行うというものです。正直どうかなと思いましたがこの方法もやってみました。 個人的にはColab側のページ構造に依存するのであまりおすすめできないことと、ノートブックに記述できないことでしょうか。できれば、処理はノートブック内に入れておきたいというのが本音です。

参考

zenn.dev

Colabを実行しているChromeブラウザで【F12キー】を押すか、ブラウザ上で右クリックして【検証】を選択してデベロッパーツールを開き、【Console(コンソール)】タブを選択し以下のコードを入力してEnterを押します。

function preventTimeout() {
  setInterval(() => {
    console.log("Keeping session active");
    document.querySelector("colab-toolbar-button#connect").click();
  }, 300000); // 5分ごとに「接続を維持」ボタンをクリック
}
preventTimeout();

一応、このやり方でもセッションは継続できました。ただ、Colab側のUI仕様などはいつ変更になるかもわからないので気を付けて使用する必要があるかなと思います。

おわりに

今回、1回あたり90分以上かかる検証を何度か行いましたが、【実験1】に他の手法を組み合わせるのがいいのかなと思います。

個人的に使おうと思ったのは正攻法である【実験1】に【実験2】(multiprocessing)または【実験4】(threading)を組み合わせるのが良いのかなと感じています。理由はノートブックを共有するだけで実現できることと、記述エラーを減らせるためです。

しかし、この実験に3日間もかけてしまうとは…トホホ。