Pipeline

アイドル中のGPUを、
余すところなく使う

MIGが使えないGPU (RTX 4090 / A6000 / L4 等) でも、1枚に複数workloadを同居させて、 単一ジョブでは40-60%アイドルになるGPUを取り戻します。 1-10 GPUのAIチーム向けの、軽量batch fleetです。

セットアップを見る マニュアル

↑ フロー図でクローラー→画像登録→Embedding の 実データ (スループット / 件数 / 前回tick) が 3 秒更新で流れる様子。

なぜGPUの空きを埋め切れるのか

単一ジョブで動かすGPUは、入力待ち / モデルロード / preprocess / 後処理で 案外40-60%アイドルしています。Pipelineは「同居させる土台」+「賢く配分する構造」の 2 段で、その隙間を別workloadに流し込みます。

MIG非対応GPUでも1枚に同居できる

サーバーGPU (A100 / H100) ならMIGで1枚を最大7枚に切り分けられますが、 RTX 4090 / 5090 / A6000 / 3090 / L4 はすべてMIG非対応です。 そこをMPS (NVIDIA Multi-Process Service) で物理共存させ、1 GPU上で複数CUDAプロセスを並行実行します。 nvidia-cuda-mps-control -d 1行で動きます。 PipelineはMPSの上のオーケストレータ層 (= どのworkloadをどれだけ流すか) を担います。

self-loopでキューを絶やさない

ワークロード自身が「次のtick」を自分でキューに入れ続ける設計です。外部のdispatcher / cronは不要で、キューが空になる瞬間が無いので、ワーカーは常に次の仕事を受け取れます。

1タスク = 1 claim、順序自由

ワーカーはキューから1件取って処理 → 完了 → 次の1件、を繰り返すだけです。バッチbarrier (= N件揃ってから次へ) が無いので、速いtaskと遅いtaskが同居しても速い方が先に流れて、GPUを空かせません。

複数workloadの同居 + 重み配分

同じワーカーfleetにN個workloadを登録できます。優先度 + 重みをOptimizerが自動調整し、「メインの推論70%、隙間の解析30%」のような同居構成を1 fleetで実現します。重い学習が来たら軽い推論を一時退避させ、終わり次第復帰します。

GPUタグで必要なワーカーへ

ワーカーごとに gpu / cpu / 任意タグを付けられます。GPU必須のtaskはGPUワーカーへ、CPUでOKなtaskはCPUワーカーへとアフィニティで振り分けられるので、GPUは「GPUが本当に要るタスク」だけで埋まります。

実測 — 1 GPU × 4 worker 同居の Before / After

社内環境 (NVIDIA RTX PRO 2000 Blackwell 16GB、 顔検出 + 顔埋め込みの推論ワークロード 6 種、 4 worker / 1 GPU 構成) で MPS 有効化前後を 30 分ずつ実測した数字です。 単一ジョブで動かしたままだと GPU は idle が長く、 複数 workload を同居させると埋まる、 という上の主張の具体例です。

指標 Before
(MPS 無し)
After
(MPS 有効)
差分
throughput (.23 / 4 worker 合算) 45.5 runs/min 52.3 runs/min +14.7%
1 task あたり平均所要 5194 ms 4261 ms -18.0%
GPU 利用率 (idle 時 nvidia-smi) 0% 36% +36 ppt
GPU メモリ使用 8.5 / 16 GB 8.8 / 16 GB +0.3 GB
worker あたり (4 worker 均し) 11.4 runs/min 13.1 runs/min +14.9%
障害 (30 分間) 0 件 0 件

計測法: started_at でフィルタした runs テーブルから 1 GPU host (.23) の 全 run を集計。 Before = MPS daemon 未起動・4 worker 直接 CUDA、 After = nvidia-cuda-mps-control -d 起動 + worker 4 個再起動。 ワークロード構成は同一 (= 顔検出 hash-detect / 顔埋込 embed / 動画→顔抽出 video-face-extract / クロール dispatcher / index writer)。 ONNX Runtime CUDA EP は MPS 経由でも問題なく動作。

示唆: 上のセクションで述べた「単一ジョブだとGPUは 40-60% アイドル」 は実測でも裏付けられました。 MPS で物理共存させただけ (= Pipeline オーケストレータ + 4 worker 同居) で、 同じハードウェア・同じワークロード構成のまま +14.7% throughput / GPU 利用率 0%→36% に変化しました。 ハードウェア追加投資ゼロでのリターンです。

主な特徴

GUIでワークロード登録

シェルコマンドやPythonモジュールをWeb UIのフォームから登録できます。開発者以外の運用担当の方でも追加・編集していただけます。

プラグイン方式

ローカル plugins/ ディレクトリに plugin.yaml + main.py を置けば即フォーム化されます。サンプルも同梱しています。

シンプルなワーカー

各ホストに1つのデーモンがHTTP heartbeatで参加します。クラスタマネージャ不要、SSH不要です (= 配信はHTTP経由)。

ライブ可視化

ダッシュボードで実行中タスク / 最近の失敗 / キュー深さ / GPU温度を3秒更新で表示します。

失敗詳細 + 再実行

各runのstderr / Python tracebackをワンクリックで展開できます。失敗したtaskは同じpkで即再投入が可能です。1件詰まって全体停止、というAirflow系の落とし穴を構造的に回避しています。

SQLiteシングルファイル

制御プレーンの全状態が1つのSQLiteファイルに収まります。バックアップ・差分・grepが容易です。

アーキテクチャ

制御プレーン (= FastAPI + SQLite + React配信) とワーカー (= ジョブ実行プロセス) は 明確に分離されています。ワーカーはHTTP heartbeatで制御プレーンに参加し、キューから タスクをclaim → process → completeします。

Control plane FastAPI + SQLite workloads / queue / runs Web UI (React) port 8001 Worker A claim → process heartbeat 10s Worker B GPU lane heartbeat 10s Worker C HTTP heartbeat / claim / complete Operator browser

画面ツアー

主要4画面のスクリーンショットです。クリックで原寸表示できます。

Dashboard — 実行中 / 最近の失敗 / キュー深さ + バージョン情報 + GPU温度スパークライン
ダッシュボード Dashboard
「いま何が動いているか」 3 panel + バージョン情報 + GPU温度のライブスパークラインを 3 秒更新で表示します。
Workloads — slug / executor / sparkline / enabled toggle / 優先度 / バッチ
ワークロード Workloads
登録ワークロードの一覧画面です。直近20件の成否を行内スパークライン + 成功率で表示します。各行から投入 / 履歴 / 編集 / 削除が行えます。
ServiceLog — Pipeline制御 + 各ホストのタブ + 動作中pulseバッジ + 一時停止
サービスログ ServiceLog
制御プレーン + 各ホストのstdoutをタブで切り替えられます。動作中はtealのpulseバッジで稼働状況を視認できます。
Deploy — 配信先ホスト + 配信パス + 配信実行 + 履歴
コード配信 Code deploy
配信先ホスト × 配信パスをWeb UIで登録し、1ボタンでrsync + service restartを一括実行できます。

これでできること

シナリオ仕組み
定期的に1行 = 1タスクのSQLクエリを並列実行したい ワークロード1つ + ワーカーN台 → 自動分散
GPUを使うモデル推論を複数ホストで回したい Pythonプラグイン + ワーカーtags=gpuでアフィニティ
失敗したタスクだけを後で再実行したい RunsDrawerの各行に「再投入」ボタン
運用担当 (= 非エンジニア) がジョブ設定を変更したい plugin.yamlinit_kwargs からフォームを自動生成
新しいGPU箱を5分で追加したい bootstrapスクリプト1行 + Deployタブで配信先登録

これは向いていない

ライセンス

MIT License です。詳細は LICENSE をご参照ください。