タイムスライシング・MPS・MIG
一言でいうと
タイムスライシングのreplicas: 5は、GPUを5つに分割しろという意味ではなく、同じデバイスを5回登録しろという意味です。スケジューラーから見える枠だけが5倍になり、VRAMは依然として分割されていない1つの塊です。
なぜ分け合って使いたくなるのか
GPUを買うと、すぐにこんなグラフを目にします。使用率は1日平均で8%。それなのに、待ち行列はいつも埋まっています。理由は単純で、Pod1つがGPU1枚を丸ごと確保するからです。Jupyterノートブックを開いたままコードを読んでいる人も、推論リクエストを毎秒1回受けるサービスも、実際にはGPUをほとんど使わずに、1枚を握ったままです。
ここから、「1枚を複数人で使えるようにしてほしい」という要求が出てきて、その答えは3つあります。3つは性質がまったく異なるのに、名前がどれも似ているため、よく混同されます。
どう動くのか
| 方式 | 何を分けるか | メモリ分離 | エラー分離 | 必要条件 |
|---|---|---|---|---|
| タイムスライシング | 実行時間だけ(コンテキストスイッチ) | なし | なし | どのGPUでも可 |
| MPS | 時間 + カーネルの同時実行 | 上限の設定は可能だが、強制的な分離ではない | 限定的 | デーモンが必要 |
| MIG | ハードウェアのパーティション | あり | あり | A100・H100クラス |
タイムスライシングは、device pluginの設定1枚で有効になります。
sharing:
timeSlicing:
failRequestsGreaterThanOne: true
resources:
- name: nvidia.com/gpu
replicas: 5
こうすると、ノードがアドバタイズするnvidia.com/gpuが、物理デバイス数×5になります。4枚なら20です。スケジューラーは20個の枠があると信じて、Pod20個を送ります。ところが、その20個が実際に出会うのは、依然としてGPU4枚で、1枚に付いた5つのPodは、同じ40GiBのVRAMを重ねて使います。スロットに保証されたメモリは0です。
そのため、こんなことが起きます。5人のうち1人が大きなモデルを載せて38GiBを確保すると、残りの4人は、PodがRunningのまま、CUDA OOMに遭います。スケジューラーには何の落ち度もありません。枠を与えると言って、枠を与えました。メモリを約束したことがないだけです。
failRequestsGreaterThanOne: trueは、この誤解を少しでも減らすためのスイッチです。Podがnvidia.com/gpu: 2を要求すると、拒否します。スライスを2つ受け取っても、それが「GPU2枚分」を意味しないからです。有効にしておくほうがよいです。
MPSは、複数のプロセスのCUDAカーネルを1つのコンテキストにまとめて、本当に同時に実行させます。コンテキストスイッチのコストがなくなってスループットが上がり、プロセスごとにメモリの上限をかけることもできます。ただし、上限は協調的な制限に近く、1つのプロセスが落ちるときに、他のプロセスまで影響を受けることがあります。
MIGだけが、ハードウェアレベルの分割です。SMとメモリコントローラーを物理的に分けるので、インスタンスごとに自分のVRAMを持ちます。隣で何をされても、自分のインスタンスは安全です。その代わり、対応するハードウェアが必要で、プロファイルが決まっているため自由には分けられず、プロファイルを変えるには、そのGPUを空にする必要があります。
現場での姿
第一に、判断基準は1つです。「このワークロード同士が、お互いを落としてもよいか」。開発用のノートブック、社内の実験、デモなら、タイムスライシングで十分で、効果も大きいです。本番の推論サービスが混ざっているなら、分離のない共有は、いつか必ず障害になります。同じクラスターでも、ノードプールを分けて、ノードごとに異なるポリシーを適用するのが普通です。
第二に、使用率のグラフにだまされないようにします。タイムスライシングを有効にすると、GPU使用率のメトリクスがきれいに上がります。しかし、その数字は「誰かがカーネルを動かしている」という意味であって、「作業が早く終わる」という意味ではありません。5人が交代で使えば、それぞれの作業は遅くなります。見るべきメトリクスは、使用率ではなく、ジョブの完了時間とOOMの発生数です。
第三に、ユーザーに何を約束したのかを書いておきます。共有を有効にした瞬間に、nvidia.com/gpu: 1というリクエストの意味が変わります。昨日までは「GPU1枚」でしたが、今日からは「順番が回ってくる枠1つ」です。この変化を周知しないと、ユーザーは自分のジョブがなぜ遅くなったのかをずっと知らないまま、インフラを疑います。
続く記事で見ること
こうした共有設定は、最終的にDaemonSetのロールアウトでノードに届けられます。すぐ次の記事では、そのロールアウトが1台のノードで止まったとき、クラスターがどんな形で残るのか、そしてその状態がなぜバグではなく設計どおりの動作なのかを見ます。