0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

nvidia-smiで回収候補GPUをPython検出する

0
Posted at

nvidia-smiで「回収候補GPU」をPython検出する

GPU Utilization 0% のGPUを、そのまま空きと扱う監視は危ない。推論プロセスが待機しているだけなら利用率は0%になるし、VRAMが空でもPIDが残ることがある。回収してよい候補は、利用率、VRAM使用量、compute process の三つを同時に見るのが安全だ。

7月30日にHugging Faceが公開した GPU Management: Why Idle GPUs Are the New Grounded Aircraft を読んで、手元の確認手順を小さなスクリプトにした。GPUを自動でリセットするものではない。運用担当が「誰にも使われていないので、ジョブを載せられそう」と確認するための候補一覧である。

今朝、模擬した3枚のGPUで動かすと、利用率もVRAMも0なのにPIDが残るGPUが一枚あった。利用率だけを見ていたら、そのGPUも空きとして通知していたはず。こういう地味な誤検知が一番困る。

Q. なぜ nvidia-smi の利用率だけでは足りない?

利用率は瞬間値だ。バッチの合間、モデルをロードしたまま入力を待つAPI、CUDAコンテキストだけを残したプロセスは0%を返しうる。一方でVRAMだけを見る方法も不十分で、CPU側の待機や小さなVRAM予約を見落とす。

そこで、次の条件をすべて満たすGPUだけを回収候補にする。

観測値 条件 意図
utilization.gpu 0 実行中の計算が見えない
memory.used 0 MiB VRAMに残ったモデルやキャッシュがない
--query-compute-apps 該当UUIDがない compute processが掴んでいない

判定の入口は、GPU単位のCSVとプロセス単位のCSVをUUIDで結合するところにある。

Q. 実装はどうする?

標準ライブラリだけで書いた。--fixture は記事中の検証用で、本番では外して実行する。GPUがないMacやCIでも判定ロジックを確認できるようにしてある。

#!/usr/bin/env python3
from __future__ import annotations

import argparse
import csv
import subprocess
from dataclasses import dataclass

GPU_QUERY = [
    "nvidia-smi",
    "--query-gpu=index,uuid,utilization.gpu,memory.used,memory.total",
    "--format=csv,noheader,nounits",
]
APP_QUERY = [
    "nvidia-smi",
    "--query-compute-apps=gpu_uuid,pid,process_name,used_memory",
    "--format=csv,noheader,nounits",
]

FIXTURE_GPUS = """0, GPU-aaaa, 0, 0, 81920
1, GPU-bbbb, 18, 4096, 81920
2, GPU-cccc, 0, 0, 81920
"""
FIXTURE_APPS = """GPU-bbbb, 4321, python, 4096
GPU-cccc, 9876, python, 0
"""

@dataclass(frozen=True)
class Gpu:
    index: str
    uuid: str
    utilization: int
    memory_used: int
    memory_total: int

def run(command: list[str]) -> str:
    result = subprocess.run(command, text=True, capture_output=True, check=True)
    return result.stdout

def rows(text: str) -> list[list[str]]:
    return [[cell.strip() for cell in row] for row in csv.reader(text.splitlines()) if row]

def parse_gpus(text: str) -> list[Gpu]:
    return [
        Gpu(index, uuid, int(utilization), int(memory_used), int(memory_total))
        for index, uuid, utilization, memory_used, memory_total in rows(text)
    ]

def active_gpu_uuids(text: str) -> set[str]:
    return {row[0] for row in rows(text)}

def reclaim_candidates(gpus: list[Gpu], active_uuids: set[str]) -> list[Gpu]:
    return [
        gpu
        for gpu in gpus
        if gpu.utilization == 0
        and gpu.memory_used == 0
        and gpu.uuid not in active_uuids
    ]

def main() -> None:
    parser = argparse.ArgumentParser()
    parser.add_argument("--fixture", action="store_true")
    args = parser.parse_args()

    gpu_text = FIXTURE_GPUS if args.fixture else run(GPU_QUERY)
    app_text = FIXTURE_APPS if args.fixture else run(APP_QUERY)
    candidates = reclaim_candidates(parse_gpus(gpu_text), active_gpu_uuids(app_text))

    if not candidates:
        print("回収候補はありません")
        return

    for gpu in candidates:
        print(f"GPU {gpu.index}: {gpu.uuid} (利用率 0%, VRAM 0/{gpu.memory_total} MiB)")

if __name__ == "__main__":
    main()

GPU-cccc は利用率0%、VRAM 0 MiBでも、FIXTURE_APPS にPID 9876がある。候補から外れるのはこのUUID照合のためだ。プロセスが何もない状態では nvidia-smi --query-compute-appsNo running compute processes found と出る環境もある。この文字列はUUIDにならないので、全GPUがプロセスなしとして扱われる。

Q. 検証結果は?

記事のコードを gpu_idle_check.py として保存し、Python 3.9.6で次を実行した。

python3 gpu_idle_check.py --fixture

出力はこれだけだった。

GPU 0: GPU-aaaa (利用率 0%, VRAM 0/81920 MiB)

GPU 1 は18%・4096 MiBなので当然残る。GPU 2 は前述のPIDがあるため残る。3枚中1枚だけを候補にできた。nvidia-smi のCSVは空白を含むので、csv.reader の後に strip() している。ここを省くとUUIDの比較が静かに外れて、実行中GPUまで候補に混ざる。

--fixture で確かめたのは、CSVの分解、UUID照合、三条件のAND判定だ。ドライバやコンテナから見えるPIDの範囲までは検証していない。実機へ入れる前に、そのノードで nvidia-smi を一度単独で叩き、CSVが5列と4列で出ることを見ておく。ここは環境差をコードで吸収したつもりにならないほうがいい。

Q. PIDが見えれば、コンテナ内のジョブまで追える?

必ずしも追えない。--query-compute-apps に出るPIDはホスト側のPIDで、KubernetesのPod名やDockerのコンテナIDではない。通知にPIDだけを載せると、調査する人は結局 ps やランタイムの情報を横断することになる。

最初はそこまで自動化しなくてよい。候補から除外されたGPUが多いなら、UUIDとPIDを一緒にログへ残して、あとから crictl ps やジョブスケジューラの割り当てと突き合わせる。逆に、候補に出たGPUだけを対象にするなら、このスクリプトの出力はGPU indexとUUIDで足りる。調査のための情報と、回収判断のための情報を混ぜないほうが運用が落ち着く。

同じ理由で、プロセス名を条件に入れていない。pythonpython3、推論サーバの実行ファイル名を列挙し始めると、名前を変えたジョブだけを見逃す。GPU UUIDがprocess一覧にあるかだけを判定に使えば、実装言語やフレームワークに依存しない。

実機では次のように使う。

python3 gpu_idle_check.py

出力をSlackへ送る、Kubernetesのノードにラベルを付ける、といった次の処理は別にしておくのがいい。回収候補と即時回収を一つのスクリプトでつなぐと、短いアイドル時間でジョブを落としかねない。自分は5分おきに記録し、連続3回候補になったUUIDだけを人が確認する運用にしている。

Q. 5分を3回見る理由は?

1回だけの判定なら、APIサーバのリクエスト間隔やデータローダーの待ち時間まで空きに見える。5分間隔で3回なら、少なくとも10分をまたいで同じ状態だったGPUだけを拾える。ここで必要なのは厳密な「アイドル時間」ではなく、次のジョブを載せる前に確認する一覧だ。

監視基盤に時系列DBがあるなら、スクリプトを常駐させず、DCGM_FI_DEV_GPU_UTIL とVRAMのメトリクスで同じ窓を作ってもいい。その場合もprocess一覧との照合は別途必要になる。メトリクスはGPUが静かなことを示し、process一覧は誰かがGPUを確保していることを示す。役割が違う。

回収候補が0枚の日が続いても、閾値をすぐ緩めない。0%のGPUを増やすために「VRAM 1 GiB未満」へ変えると、モデルをロード済みで一時停止しているサーバまで候補に寄る。空き容量を増やす施策は、利用者の明示的なリリースやスケジューラの予約期限と組み合わせるのが本筋だと思っている。

Q. あとで誤通知を調べるには何を残す?

通知本文だけを残すと、なぜ候補になったかが消える。少なくとも観測時刻、GPU UUID、利用率、使用VRAM、process一覧に同じUUIDがあったかを一行で残す。候補でなかったGPUも、集計値だけは残しておくとよい。翌日に「GPU 2が候補にならなかったのはバグか、それともPIDがあったのか」を判別できる。

このスクリプトはCSVの列数が想定と違うと例外で止まる。無理に0として扱わないためだ。ドライバ更新やラッパーの変更で出力形式が変わったとき、空きGPUを大量に通知するより、監視が壊れたと分かったほうがましだった。通知の監視では、この終了失敗自体も拾うようにしている。

運用ログを見始めると、候補判定とは別の偏りも見える。特定のGPUだけが毎回空くなら、GPUの故障を疑う前に、スケジューラのresource requestやAffinityを確認する。逆に全GPUが同時に候補になる時間があるなら、夜間バッチの終了時刻に合わせて次のジョブを詰められる。回収候補の一覧は、空きの検出だけでなく配置の癖を読む材料になる。

Q. このまま自動回収してよい?

よくない。これは「GPUに何も載っていない」ことを、通常のcompute processに限って見る検査だ。MIG構成、コンテナランタイムの見え方、graphics process、GPU外に状態を持つ分散ジョブは別に確認がいる。とくに共有ノードでの nvidia-smi --gpu-reset を、この結果だけで実行してはいけない。

判定を厳しくすると、回収できるGPUは少なく見える。それでよい。空きGPUの通知は速さより誤通知の少なさが大事で、1枚を取り逃すより、動いている推論を止めるほうが高くつく。

おわりに

GPU運用で見るべきなのは利用率の低さではなく、実際に誰が掴んでいるかだった。nvidia-smi のGPU一覧とprocess一覧をUUIDで突き合わせれば、0%という一瞬の値を通知根拠にしなくて済む。

まずは読み取り専用の候補出しとして入れ、実機の出力を数日残す。候補が本当に使われていなかったかを確かめてから、通知先や自動化を足す。この順番なら、GPUを寝かせたままにする損と、回収を急ぎすぎる事故の両方を小さくできる。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?