ステータス: ドラフト (2026-10-07更新、2026-10-09借用上限の実測を補強)。 公式ドキュメントと論文で確認した記述に基づいており、**第3節終わりの3つの実測(ギャングスケジューリングの比較、コホートの借用とプリエムション、借用上限と回収速度)**は、使い捨ての単一ノードクラスターで直接測定した値です。残りの節はまだ実測していません(未実測)。 記事の終わりの「確認したバージョンと日付」および「確認できなかったこと」を先にお読みください。 作成ルール: 実測データを基本とし、測定できなかったものは「未実測」と表記します。
目次
- 1. デフォルトスケジューラーの限界
- 2. Kueue: 承認レイヤー
- 3. Volcanoと選択基準
- 4. トポロジ認識配置
- 5. 優先度とプリエムションの設計
- 6. GPU共有と隔離の限界
- 7. Dynamic Resource Allocation(DRA)の現状
- 8. 容量計画
- 小規模な共有でよくある落とし穴
- 実際に試してみる
- 面接で聞かれそうな質問
- よくある誤解
- 参考文献
- 外部の根拠
- 確認したバージョンと日付
GPUクラスターを複数のチームで分割して使用するには、3つの要素が必要です。チームごとの割り当てを保証しつつ余ったリソースを貸し借りできるようにするクォータ、分散学習のように複数同時に起動する必要があるジョブのためのギャングスケジューリング、サービングを維持しながらバッチジョブを押し出すプリエムションポリシーです。デフォルトスケジューラーは、これらのいずれも直接提供しません。この記事では、補完手段の設計の違い、隔離の限界、そして容量を計算する手順について取り上げます。
この記事を読むことで面接で説明できるようになること
- デフォルトスケジューラーのGPUモデル(整数拡張リソース)がギャングスケジューリングや公平な共有において不十分な理由、そしてKueueは承認レイヤーでありVolcanoはスケジューラーであるという違い。
- time-slicing、MIG、MPSが分割するものと隔離できないもの、信頼レベル別の選択。
- 要求率と遅延目標からGPU数を逆算する手順(スループット、同時実行数、キー・バリュー(KV)キャッシュ、余裕率、障害対策)と使用率指標の落とし穴。
1. デフォルトスケジューラーの限界
- 整数拡張リソース: デバイスプラグインのドキュメントには、拡張リソースは整数のみでサポートされ、オーバーコミットできず、コンテナー間で共有できないと記載されています。リクエストとリミットは同じでなければなりません。
- ポッド単位の決定: kube-schedulerは、ポッドを1つずつフィルタリングしてスコアを付け、ノードを選びます。8個のGPUを必要とするジョブの8個のポッドのうち6個だけが枠を獲得した場合、その6個がGPUを握ったまま残りのポッドを待ちます。これがギャングスケジューリングが必要とされる理由です。
- ネイティブなギャングスケジューリングはまだデフォルトではありません: Kubernetes 1.37において
PodGroupAPI(scheduling.k8s.io/v1beta1)とGenericWorkloadゲートはベータ版ですが、デフォルトでは無効です。ドキュメントには、最小数(minCount)の保証は初期配置にのみ適用されると記載されています。 - キューイングと公平性がありません:
ResourceQuotaはrequests.nvidia.com/gpuのようにネームスペースの総量のみを制限し、待ち行列、借用、返却機能はありません。 - プリエムションはポッド単位で行われます: 犠牲となるポッドにはグレースフルシャットダウンの猶予期間があるため枠が空くまでに時間がかかり、PodDisruptionBudgetの遵守はベストエフォートです。
preemptionPolicy: Neverは他のポッドをプリエムションしないという意味であり、自分がプリエムションされないという意味ではありません。
2. Kueue: 承認レイヤー
flowchart LR
J["Job (queue-name 레이블)"] --> LQ["LocalQueue (네임스페이스)"]
LQ --> CQ["ClusterQueue (쿼터, cohort)"]
CQ -- "쿼터 예약과 승인" --> W["Workload 승인, 파드 생성"]
W --> KS["kube-scheduler가 노드 배치"]
KS --> DP["디바이스 플러그인 또는 DRA"]
Kueueのドキュメントには、ポッドをノードに配置する作業はkube-schedulerが、オートスケーリングはcluster-autoscalerが担うと記載されています。Kueueは、いつジョブを開始(ポッド作成)し、いつ停止するかを決定します。
| 概念 | 役割 |
|---|---|
| ResourceFlavor | ノードグループの特性(GPUモデルなど)をラベルとTaint/Tolerationで表現 |
| ClusterQueue | リソースプール。クォータ(nominalQuota, borrowingLimit, lendingLimit)、プリエムション、公平共有ルール |
| LocalQueue | チームのネームスペースからジョブを投入するポイント |
| cohort | 未使用のクォータを相互に貸し借りするClusterQueueのグループ |
apiVersion: kueue.x-k8s.io/v1beta2
kind: ClusterQueue
metadata: {name: team-a}
spec:
cohortName: all-teams
queueingStrategy: BestEffortFIFO
preemption: {reclaimWithinCohort: LowerPriority, withinClusterQueue: LowerPriority}
resourceGroups:
- coveredResources: ["nvidia.com/gpu"]
flavors:
- name: gpu-pool
resources: [{name: nvidia.com/gpu, nominalQuota: 8, borrowingLimit: 4}]
Jobは kueue.x-k8s.io/queue-name ラベルでLocalQueueを選択し、優先度はポッドとは切り離された WorkloadPriorityClass(ラベル kueue.x-k8s.io/priority-class)で付与します。プリエムションには、優先度ベースのデフォルト方式と、ドミナントリソースシェア(DRS)を比較する公平共有方式があります。最新のv0.20.0(2026-09-30)では v1beta1が提供されなくなり、 v1beta2のみが提供されます。Deployment、StatefulSet、LeaderWorkerSetもサポートされているため、サービングと学習を一緒に管理できます。マルチクラスターディスパッチであるMultiKueueはベータ版です。
waitForPodsReadyは、制限時間内にすべてのポッドの準備が整わない場合、ジョブを中断してキューに戻します。ドキュメントではこれをタイムアウトベースの実装と呼び、順次承認のために承認が遅くなる可能性があるとしています。この記事では、ネイティブなギャングスケジューリングとは区別して扱います。
3. Volcanoと選択基準
Volcanoは独立したバッチスケジューラーです。 PodGroupの minMemberがギャング単位であり、 Queueは weight(相対シェア)、 capability(上限)、 guarantee, reclaimableを持ちます。スケジューラーは enqueue, allocate, preempt, reclaim, backfill アクションを実行し、 gang, priority, drf(ドミナントリソースフェアネス、DRF)、 proportion, binpack などのプラグインを設定ファイルの階層として構成します。v1.15.0でギャング認識型プリエムションがアルファ版として導入され、最新の安定版ラインはv1.15.3(2026-09-30)です。NVIDIAのKAI Schedulerも独立したスケジューラーとして、階層型キュー、時間ベースの公平共有、トポロジ認識を謳っています(最新バージョンは確認できていません)。
| 基準 | Kueue | Volcano |
|---|---|---|
| 位置づけ | アドミッションコントローラー、kube-schedulerを維持 | 独立したスケジューラー |
| ギャング | タイムアウト方式 | PodGroup 最小数の保証 |
| 公平共有 | cohort借用、DRS公平共有 | キューの重み、DRF、proportion |
デフォルトスケジューラーを維持したままチーム別のクォータと借用が目的の場合はKueueが、ギャングとキューをスケジューラー内で強力に使いたい場合はVolcanoやKAIが候補になります。両者を同じポッドに重ねて使う構成のサポート範囲は確認できていません。
実測: 4つのポッドが同時に起動する必要がある2つのジョブが競合する場合
第1節の「ポッド単位の決定」が実際にデッドロックを引き起こすのか、2つのツールがそれをどのように解決するのかを実際に測定してみました。
環境と方法。 4 vCPU、4GiBの仮想マシン内の使い捨て単一ノードk3s(v1.36.5)において、ノードステータスに拡張リソース example.com/gpu を8個広告しました(実際のGPUはなく、ポッドは sleep のみを行います)。1つのジョブは4つのポッドからなり、ポッドごとに2つのリソースを要求するため、1つのジョブで8個すべてを使い切ります。ジョブAとBを同時に投入すると、需要16、供給8となります。「1つのジョブの4つのポッドすべてがRunningになった時刻」を測定し、受け入れられたジョブを削除した後に待機していたジョブの4つすべてが起動するまでの時間も測定しました(ポッド終了猶予0秒、3回繰り返し)。Kueue v0.20.0、Volcano v1.15.3をそれぞれのマニフェストでインストールしました。
| 方式 | 結果 |
|---|---|
| デフォルトスケジューラー(ポッドをA、B交互に作成) | 60秒間、Aが2個、Bが2個のみ実行されて停止。どちらのジョブも4個を満たすことができず デッドロック(待機理由: 拡張リソース不足、プリエムション可能なポッドなし) |
| Kueue(ジョブを一時停止状態で投入) | 一方のジョブのみが受け入れられ、 2.1秒(3回すべて)以内に4個が実行され、もう一方のジョブはポッドがまったく作成されない。受け入れられたジョブを削除すると、 3.0〜3.6秒 後に待機中のジョブ4個が実行される |
Volcano(minAvailable: 4) |
一方のジョブのみ 3.3〜4.1秒 以内に4個が実行され、もう一方のジョブは実行数0個。受け入れられたジョブを削除すると、 3.6〜4.2秒 後に待機中のジョブ4個が実行される |
- デッドロックは実際に発生します。 ポッドが交互に作成されると、デフォルトスケジューラーは両方のジョブにリソースを半分ずつ割り当て、残りのポッドは永遠に枠を獲得できなくなります。リソースを握ったままお互いを待っている状態です。ポッドの作成順序が1つのジョブに偏れば発生しないこともあるため、これは最悪に近い順序での結果です(順序を入れ替えてのテストは行っていません)。
- 両方のツールとも「すべてか無か(All-or-Nothing)」で動作しました。 一部分のみが実行された瞬間は一度も観察されませんでした。方式は異なり、Kueueはジョブ単位でクォータを確認して受け入れられるまでポッドを作成せず(ジョブが一時停止状態)、Volcanoはポッドグループの最小数を満たせる場合にのみスケジューラーが一括して枠を与えます。
- 受け入れられる側が毎回同じとは限りませんでした。 初回のテストではB、その後の3回はAが先に受け入れられました。同じ優先度で投入がほぼ同時に発生した場合、順序に頼るべきではありません。順序が重要である場合は、優先度を明示します。
- インストールのコスト。 どちらのツールも、マニフェストの適用から準備完了まで約20秒でした(イメージの初回プル時間を含む、各1回)。
- テストのエラー(記録)。 初回のテストでVolcanoの待機時間が35秒になりましたが、
sleepが終了シグナルを無視してポッドが30秒の猶予期間を使い切って終了した時間が混ざった値でした。猶予を0秒に設定して再測定し、上記の3〜4秒を得ました。プリエムションや返却時間を測定する際、終了猶予が時間を支配するという第1節の記述(犠牲ポッドの正常終了猶予)と同じ話になります。
この値は、単一ノード、ダミーリソース、2つのジョブ、イメージがすでに存在する軽量なコンテナーという条件でのみ意味を持ちます。ノードが増えると、スケジューラーのスループットと場所の選択がコストを左右します。また、Kueueがデッドロックを回避できたのは、クォータ(8個のリソース)が実際の利用可能リソースと一致していたためだと考えられます。Kueueのドキュメントには、クォータと実際の利用可能リソースが一致していない場合、デッドロックする可能性があると記載されています。本番環境の復旧時間のスケールは分単位です(以下の外部根拠におけるPhillyの2〜3分のタイムアウトとKueueの30分のデフォルト制限時間)。この記事の2〜4秒は、競合のない正常系のパスでの値です。
実測2: コホートの借用、返却、優先度プリエムション
上記の環境(単一ノードk3s、拡張リソース8個、 sleep ジョブ、終了猶予0秒)において、今度は1ポッド(1リソース)のジョブを複数立ち上げてキューのポリシーを確認しました。Kueue v0.20.0であり、2つのチーム(team-a、team-b)がそれぞれ4個のクォータを持ち、互いの余ったクォータを借りることができるコホートです。
| 状況 | 結果 |
|---|---|
reclaimWithinCohort: Never. team-aが8個を投入し、自身のクォータ4個にteam-bの割り当て4個を追加で借用 |
8個すべてが3.7秒以内に実行。続いてteam-bが自分のクォータ4個を要求すると、 60秒間0個(追い出されたジョブ0件)。team-aのジョブ4個を終了させると、3.1秒後にteam-bの4個が実行 |
reclaimWithinCohort: Any. 同じ状況 |
team-aの8個が1.8秒。team-bの4個が 4.2秒 以内に実行され、team-aは4個に減少し、追い出されたジョブが4件(イベント Preempted to accommodate a workload)。team-bを終了させると、team-aの8個が3.0秒後に復帰 |
withinClusterQueue: LowerPriority, クォータ4、低い優先度の4個が実行中 |
優先度の高い2つが 3.0秒 以内に実行され、優先度の低いものは2つ残る。追い出されたジョブは一時停止状態に戻って待機。優先度の高いものを削除すると、優先度の低い4つが3.0秒後に復帰 |
Volcano v1.15.3 デフォルト設定(actions: "enqueue, allocate, backfill")、キューの両方 qa、qb(重みは同じ、 reclaimable: true) |
qa が 8個をすべて使い切った後に qb が 4個を要求すると 45秒間 0個。デフォルト設定には reclaim アクションがないため |
同じ Volcano で actions に reclaim 追加 |
qb 4個が 10.3秒 以内に実行され、qa は 4個に減少(イベント Evict ... reclaim)。qb を削除すると qa 8個が 1.8秒後に復帰 |
- 借りた分の割り当てを返却してもらうことはデフォルトの動作ではありません。 Kueue は
reclaimWithinCohortをNeverに設定すると、借りた側が自主的に終了するまで待ち(ここでは60秒以上待機)、Volcano はデフォルトのスケジューラー設定にreclaimがないためプリエムションが発生しませんでした。第5節の「サービングが借用分を取り戻すとバッチが押し出される」という設計は、この設定を有効にして初めて成立します。 - 取り戻すまでの時間は数秒から十数秒であり、終了猶予期間が 0 であるためです。 実際の学習ジョブはチェックポイントを保存して終了するため、猶予期間分が加算されます(上記テストのエラーログと同じ理由)。
- 追い出されたジョブは消えずにキューに戻り、 リソースが空くとすべて再実行されました(3.0秒、1.8秒)。
- 小さなジョブを大量に投入する場合は Kueue の方が遅くなりました。 リソース 8個、1つのジョブがリソース 1個を 1秒間使用するジョブ N個を一度に投入し、すべて完了するまでの時間を測定しました。
| ジョブ数 | デフォルトスケジューラー(拡張リソース 8に制限) | Kueue(単一キュー、クォータ 8) | 理想値 |
|---|---|---|---|
| 100 | 55.0秒 | 74.0秒(+35%) | 約12秒 |
| 400 | 231.0秒 | 約343秒(+48%) | 約50秒 |
Kueue の 400個は、30秒ごとに進行状況を記録した別のテスト(約1.3個/秒で一定に完了)で測定した値です。最初のテストは完了数をカウントする方式のエラーにより値を取得できませんでした。どちらの方式も理想値(リソース 8個が休まない場合)の 5〜7倍であるため、時間を支配したのはスケジューラーではなく、この 4 vCPU の VM で Pod を作成および削除するコストであり、Kueue はジョブあたり約 0.2〜0.3秒の承認処理を追加していました(計算値)。小規模ではこの程度の差は Pod の起動時間に埋もれますが、短いジョブを毎秒数十個ずつ投入するワークロードであれば、キュー階層がスループットの制限要因になる可能性があることを意味します。
- 制限。 ジョブ数が数十個以下(大量テストは最大 400個)であり、ノードが 1つで擬似的なリソースである点です。借用上限は以下の実測 3 で短く測定しており、優先度が混在したキューの長期的な動作(低い優先度の飢餓、StrictFIFO と BestEffortFIFO の待機分布)は測定できていません(未測定)。
実測 3: 借用上限と回収速度
Mac(Apple M1 Max)上の Docker Desktop(メモリ 8GB、CPU 10個)上の kind 単一ノード(Kubernetes v1.36.1)で
Kueue v0.20.0 を用いて測定しました。擬似拡張リソース、 sleep ジョブ、終了猶予 0秒は実測 2 と同様です。
1つのコホートに team-a(クォータ 3)と team-b(クォータ 5)を配置し、 reclaimWithinCohort: Any で取り戻す機能を
有効化しました。team-a がリソース 1個分のジョブ 10個を投入し、続いて team-b が自身のクォータ 5個を要求しました。
| team-a の借用上限 | team-a の最大実行数 | team-b がクォータ 5個を取り戻すまで | 追い出されたジョブ |
|---|---|---|---|
| なし | 8個(クォータ 3 + 借用 5) | 6.4–7.7秒(中央値 6.8秒) | 5個 |
| 4 | 7個 | 5.2–5.8秒 | 4個 |
| 1 | 4個 | 1.7–2.7秒(中央値 2.0秒) | 1個 |
- 借用量は上限で正確に停止しました。 8回のサンプルのすべてで最大実行数が「クォータ + 上限」と一致し、 約 12分間にわたり 1秒ごとに記録した 2,064個のキュー状態においても、上限を超えた瞬間はありませんでした。
- 取り戻すまでの時間は借りていた量に比例しました。 Kueue は借りていたジョブを約 1.1秒に 1つのペースで 追い出しました。そのため、借用上限は公平性のメカニズムであると同時に、「サービングが自身の割り当てを取り戻すまでに最大何秒かかるか」を 決定するノブでもあります。犠牲になるジョブごとに終了猶予期間(チェックポイントの保存時間)が加算される点は実測 2 と同様です。
- 一時的に空いたクォータを借りたジョブは、1〜2秒で再び追い出されました。 あるチームのジョブが終了してクォータが空いた 瞬間に別のチームの待機中ジョブがその分を借りたものの、本来のチームの待機中ジョブが戻ってきたことですぐに取り戻されたということが、 最初のサイクル(約 3分)の間に 2回発生しました。バッチジョブの開始コストが大きい学習であれば、このような短い借用は 損失となるため、借用上限を小さく設定するか、バッチに低い優先度を割り当てる方が得策です(第5節)。
- 制限。 同じ Docker を別のクラスターと共用して VM のメモリが枯渇した区間の実行結果はすべて 無効として破棄しており、条件ごとの有効なサンプルは 2〜3個です。20分以上の長期実行と優先度が混在するキューについては 3回試行したものの、過負荷により無効となったため測定できませんでした(未測定)。
4. トポロジ認識バッチ処理
ノード内の NVLink とノード間の InfiniBand(IB)の近接性が、学習の通信速度を大きく左右します。Kueue の Topology Aware Scheduling(TAS)は v0.14 からベータであり、デフォルトで有効です。ノードラベルによりブロックやラックなどの階層を Topologyに定義して ResourceFlavor.spec.topologyNameで接続し、Pod アノテーションの podset-required-topologyや podset-preferred-topologyで要求レベルを指定します。すべての Pod とノードを追跡するためメモリ使用量が増加するとドキュメントに記載されています。Volcano は HyperNode(ドキュメントのサンプル API は topology.volcano.sh/v1alpha1)でティアを定義し、ジョブにハードまたはソフトの制約を与えます。マルチノード NVLink は NVIDIA DRA ドライバーの ComputeDomainが公式サポート機能です。
5. 優先度とプリエムションの設計
サービングには保証クォータと高い優先度を、バッチ学習には低い優先度と借用クォータを割り当てます。サービングが借用分を取り戻すと(reclaimWithinCohort)バッチが押し出されるため、バッチはチェックポイントによる中断への耐性を持たせる必要があります。犠牲になる Pod の終了猶予期間は再スケジューラの遅延につながるため、チェックポイントの保存時間のみを残して短縮します。また、Kubernetes のドキュメントによると、スケジューラーは DRA リソースを使用する Pod をプリエンプトできません。
6. GPU 共有とアイソレーションの限界
| 方式 | 分割するもの | アイソレーション |
|---|---|---|
| time-slicing | 時間 | メモリおよび障害のアイソレーションなし、すべてのプロセスに均等な時間を配分 |
| MIG(Multi-Instance GPU) | ハードウェア分割 | メモリと障害のアイソレーション、最大 7インスタンス |
| MPS(Multi-Process Service) | ストリーミングマルチプロセッサ(SM)の同時実行 | 致命的なエラーが同じ GPU上の他のクライアントに影響を与える可能性がある |
time-slicing は replicasの分だけリソースを水増しして広告しており、DCGM(Data Center GPU Manager) エクスポーターはこのモードでコンテナごとのメトリックを関連付けることができません(GPU Operator のドキュメント)。MIG は Ampere 世代以降の一部 GPU(A100、H100、B200 など)でサポートされており、確認した NVIDIA のサポートリストにコンシューマー向け GPU は含まれておらず、構成を変更するには該当 GPU 上でユーザーワークロードが稼働していない状態である必要があります。MPS はドライバー r610 以降で静的 SM 分割を有効にすると部分的なアイソレーションが提供され、モニタリングツールはクライアントの使用量を MPS サーバープロセスに合算します。そのため、相互に信頼できないテナントには MIG または専有ノードを、同一チームの軽量なジョブには time-slicing または MPS を使用します。Kueue のクォータはアカウンティングツールであり、セキュリティ境界ではありません。
7. Dynamic Resource Allocation(DRA)の現状
DRA では ResourceClaim, DeviceClass, ResourceSliceを使用してデバイスを属性(共通表現言語(CEL)式)で要求できるようになります。機能ゲートのステータス(2026-10-05 確認)は以下の通りです。
| 機能 | ステージ |
|---|---|
| コア DRA | stable(1.34、ゲートは 1.35 でロック、公式ページには「Stable since v1.35」と記載) |
| 優先度リスト、管理者アクセス | stable(1.36) |
| 既存の拡張リソース名のマッピング、デバイスのテイント | stable(1.37) |
| 分割可能デバイス、消費可能容量、バインディング条件 | beta(1.36) |
| ワークロード単位の ResourceClaim | beta(1.37、デフォルト無効) |
| ノード割り当て可能リソース、互換グループ | alpha |
NVIDIA DRA ドライバー(kubernetes-sigs/dra-driver-nvidia-gpu、v0.5.0、2026-08-19)の README(main)では、 ComputeDomainを公式サポートとし、GPU 割り当ては公式サポートではなくチャートではデフォルトで無効になっていると記載されています。ドキュメントサイトには MIG、time-slicing、MPS がサポート機能として列挙されていますが、ステージが明記されていないためどちらが最新であるかは確認できませんでした。Kueue v0.20 のリリースノートには DRA デバイスの可用性チェックがアルファゲートとして含まれており、Volcano v1.15 はキューのクォータ計算に DRA リソースを組み込んでいます。
8. キャパシティプランニング
λ(요청률), 입출력 길이, 지연 목표 -> 복제본 1개의 실측 처리량과 동시성
-> 처리량 기준 대수, 동시성(Little 법칙) 기준 대수, 키-값(KV) 캐시 한도 확인
-> 큰 쪽 선택 -> 여유율 -> 장애 대비(N+k) -> 병렬화(텐서 병렬, TP) 단위로 올림
説明用の例です。基準点は筆者の実験環境で測定した値であり、24GB クラスのコンシューマー向け GPU において Qwen3.8-27B(NVFP4)が同時 4リクエストの合算で 138 tok/s であり、KV キャッシュは約 57,000トークンです。リクエストレート 0.5 req/s、入力 1,500トークン、出力 400トークンを想定します。
- スループット:0.5 × 400 = 200 tok/s ÷ 138 = 1.45 であるため 2台。
- 同時実行数:リクエストあたり 138 ÷ 4 = 34.5 tok/s であれば応答時間は約 11.6秒であり、リトルの法則により 0.5 × 11.6 ≈ 5.8個が同時に処理されます。レプリカあたり同時 4個の制限があるため最低 2台(8スロット)であり、スロット使用率は約 72% です。
- KV の確認:6 ×(1,500 + 400)= 11,400トークンであり、57,000以内です。
- 負荷変動に対するマージン 30% と障害への備えとして 1台を追加し、合計 3台とします。
モデルが複数の GPU にまたがる場合(テンソルパラレル TP)、レプリカ単位が GPU 数に応じて大きくなるため、台数に TP サイズを乗算します。また、1つのノードが停止するとレプリカ単位で容量が減少します。
使用率メトリックの落とし穴:NVML(NVIDIA Management Library)の utilization.gpuは、サンプル期間中に少なくとも 1つのカーネルが実行されていた時間の割合を示すものであり、SM がどれだけ使用されているかを示すものではありません。DCGM の SM_ACTIVE, PIPE_TENSOR_ACTIVE などのプロファイル指標の方がより詳細です。コストの観点からは、割り当てられた GPU 時間に対する実際の作業時間の割合(アロケーション率)と、サービングの指標(キューの長さ、KV キャッシュの使用率、目標レイテンシを維持したスループット)を組み合わせて確認します。
小規模な共有環境でよくある落とし穴
筆者の実験環境はコンシューマー向け GPU(6〜24GB)が中心であり、マルチノード学習や Kueue は使用していないため、本節は大規模なマルチテナンシーの証拠ではなく、小規模な共有環境で遭遇しやすい落とし穴を一般化したものです。
- time-slicing ではメモリは分割されません:
replicasを増やし、failRequestsGreaterThanOne: trueにより1つのPodが複数の割り当てを要求できないように制限しても、GPUメモリ(VRAM)は分割されません。1つのPodがVRAMを使い切ると、残りはメモリ不足(OOM)で終了します。適用は1ノードずつ行う方が安全です。複数のノードに一度に適用すると、コンテナランタイムの設定問題によりGPUを長期間使用できなくなる事故が発生する可能性があるためです。 - 1枚のカードを共有して使用するサービスが、作業単位の障害を引き起こします: 異なるサービスが1枚のカードを共有して使用する場合、OOMによって呼び出しの一部が失敗します。1つの作業が数十回の呼び出しで構成されている場合、呼び出しの失敗率が作業単位で増幅され、完了する作業がほとんどなくなる可能性があります。この原理は算術で確認できます(説明用:呼び出しあたり5%、30回の場合は成功率約21%)。カードの数に合わせてサービスの数を整理し、ユーザーが直接使用するサービスをバッチ系の生産作業よりも前に配置するという優先順位を記録しておくことをお勧めします。
- 静的配置: ノードあたりGPUが1つの場合、レプリカが重複できないため
nodeSelectorでノードを固定し、デプロイ戦略をRecreateにします。メモリ使用率の設定(vLLMのGPUメモリ使用率など)は、ドライバーやWSLなどの環境が事前に占有するメモリのため、デフォルト値で起動できない場合があり、下げる必要があるケースが存在します。 - 信頼できないPodのGPUクォータは0: ユーザーコードを実行するネームスペースの
ResourceQuotaにrequests.nvidia.com/gpu: "0"を設定し、GPUの要求をブロックします。以下の「実際に試してみる」でこの動作を確認します。
実際に試してみる
読み取り専用または一時的なネームスペースで終了します。この記事の執筆時に実行して検証したわけではありません。
# 읽기 전용: 노드별 GPU 광고량과 할당 상태
kubectl get node -o custom-columns='NAME:.metadata.name,GPU:.status.allocatable.nvidia\.com/gpu'
kubectl describe node NODE | grep -A8 "Allocated resources"
# 임시: 쿼터 0에서 거절, 쿼터 해제 후 Pending 사유 확인 (정리 포함)
pod() { cat <<EOF
apiVersion: v1
kind: Pod
metadata: {name: want-gpu}
spec:
containers:
- {name: c, image: busybox, command: ["sleep","60"], resources: {limits: {nvidia.com/gpu: $1}}}
EOF
}
kubectl create ns lab-gpu
kubectl -n lab-gpu create quota gpu-zero --hard=requests.nvidia.com/gpu=0
pod 1 | kubectl -n lab-gpu create --dry-run=server -f -
kubectl -n lab-gpu delete quota gpu-zero
pod 99 | kubectl -n lab-gpu create -f -
kubectl -n lab-gpu describe pod want-gpu | tail -8
kubectl delete ns lab-gpu
最初の要求はクォータ超過により拒否され、2番目のPodはPendingのまま残り、GPU不足をイベントで通知する必要があります。どちらの側もGPUを占有しません。Kueueのハンズオンは、他の人と共有するクラスターではなく、使い捨てのクラスター(kindなど)でのみ行ってください。インストールのコマンド(kubectl apply --server-side -f https://github.com/kubernetes-sigs/kueue/releases/download/v0.20.0/manifests.yaml)は、クラスター全体にカスタムリソース(CRD)を追加します。
面接で出題されそうな質問
- デフォルトのスケジューラーで分散学習を実行すると、どのような問題が発生しますか? Pod単位で配置され、一部だけがGPUを保持したままデッドロックに陥る可能性があります。Volcanoの
PodGroup、ベータ版であるネイティブのPodGroup、またはKueueの時間制限方式で補完し、最後の方式は時間制限ベースの実装であることを把握しておきます。 - KueueとVolcanoをどのように選択しますか? Kueueは、kube-schedulerを維持したままクォータ、借用、フェアシェアを追加する場合に適しています。Volcanoは、新しいスケジューラーを運用することになっても、ギャング、キュー、トポロジを強く活用したい場合に選択します。
- サービングを維持しながらバッチ学習を実行するにはどうすればよいですか? サービングに保証クォータと高い優先度を与え、バッチはcohortから借用させます。回収時にプリエムプ(先占)されるため、チェックポイントと終了猶予期間を設計します。
- GPU共有方式と隔離の限界は何ですか? time-slicingにはメモリ・障害の隔離がなく、MIGはハードウェア隔離ですがサポートされるGPUが制限され、MPSはエラーが伝播する可能性があります。信頼できないテナントにはMIGまたは専用ノードを使用します。DRAはこれを属性ベースで表現する方向性であり、機能ごとの成熟度が異なります。
- リクエスト率とレイテンシの目標からGPU数をどのように算出しますか? スループット、リトルの法則による同時実行数、KVキャッシュの上限をそれぞれ計算して大きい方を選択し、余裕率、N+k、並列化単位で引き上げます。使用率は
utilization.gpuだけでなく、SMアクティビティとサービング指標を合わせて確認します。
よくある誤解
- Kueueはスケジューラーではありません。Podをノードに配置しません。
- time-slicingによってメモリが分割されるわけではありません。謳われている数は隔離ではなく同時アクセスの許可数です。
- GPU使用率100%は飽和を意味するわけではありません。カーネルが1つでも実行されたことを意味している場合があります。
- ネイティブのギャングスケジューリングがベータ版として導入されたからといって、KueueやVolcanoが不要になるわけではありません。デフォルトでは無効であり、キューとフェアシェアは別物です。
参考資料
- https://kubernetes.io/docs/concepts/scheduling-eviction/gang-scheduling/
- https://kubernetes.io/docs/concepts/scheduling-eviction/pod-priority-preemption/
- https://kubernetes.io/docs/concepts/extend-kubernetes/compute-storage-net/device-plugins/
- https://kubernetes.io/docs/concepts/scheduling-eviction/dynamic-resource-allocation/
- https://github.com/kubernetes/website/tree/main/content/en/docs/reference/command-line-tools-reference/feature-gates
- https://kueue.sigs.k8s.io/docs/overview/
- https://kueue.sigs.k8s.io/docs/concepts/cluster_queue/
- https://kueue.sigs.k8s.io/docs/concepts/preemption/
- https://kueue.sigs.k8s.io/docs/concepts/topology_aware_scheduling/
- https://kueue.sigs.k8s.io/docs/concepts/multikueue/
- https://kueue.sigs.k8s.io/docs/tasks/manage/setup_wait_for_pods_ready/
- https://github.com/kubernetes-sigs/kueue/releases/tag/v0.20.0
- https://volcano.sh/en/docs/podgroup/
- https://volcano.sh/en/docs/queue/
- https://volcano.sh/en/docs/network_topology_aware_scheduling/
- https://github.com/volcano-sh/volcano/releases/tag/v1.15.0
- https://github.com/kai-scheduler/KAI-Scheduler
- https://github.com/kubernetes-sigs/dra-driver-nvidia-gpu
- https://docs.nvidia.com/datacenter/cloud-native/gpu-operator/latest/gpu-sharing.html
- https://docs.nvidia.com/datacenter/cloud-native/gpu-operator/latest/gpu-operator-mig.html
- https://docs.nvidia.com/datacenter/tesla/mig-user-guide/supported-gpus.html
- https://docs.nvidia.com/deploy/mps/architecture.html
- https://docs.nvidia.com/deploy/nvml-api/api/structnvmlUtilization__t.html
- https://docs.nvidia.com/datacenter/dcgm/latest/user-guide/feature-overview.html
外部の根拠
直接測定できなかった規模とデッドロックの実際の頻度は、以下の資料で確認しました。論文の数値はPDFの原文から直接確認し、要約のみが存在する数値は使用していません。
| 主張 | 資料 | 確認した内容 |
|---|---|---|
| 一部のリソースのみを獲得して待機する問題は、本番環境のスケジューラーで実際に取り上げられていた | Jeonら, USENIX ATC 2019, Analysis of Large-Scale Multi-Tenant GPU Clusters for DNN Training Workloads(arXiv 1901.05758) | MicrosoftのPhillyスケジューラーは、2〜3分のタイムアウト内に必要なGPUをすべて取得できない場合、取得したリソースを返却し、2分のバックオフ後に再試行する。75日間、96,260個の作業を追跡した結果、待機の大部分が断片化遅延(8 GPU超の作業は97.9%)。デッドロックの発生頻度を測定したものではない |
| 時間制限方式は真のギャングスケジューリングではない | Kueue KEP-349(All-or-nothing)、 waitForPodsReady ドキュメント |
クォータと利用可能なリソースが一致しない場合、デッドロックに陥る可能性があり、デフォルトの制限時間は30分、タイムアウト時は60秒から2倍ずつ最大3,600秒のバックオフを行う。順次承認はスループットを犠牲にして信頼性を得るものであると自ら記載している |
| Volcanoのギャング | Volcanoドキュメントのgangプラグイン、PodGroup | 最小実行数が満たされた場合にのみすべてのPodをスケジュールする「すべてか無か(All-or-nothing)」方式 |
| キューの待機時間の規模 | Kueueリポジトリのパフォーマンステスト設定(test/performance/scheduler) |
キュー30個、クォータを大幅にオーバーサブスクライブした負荷における全体で約351秒(上限425秒)、承認までの平均待機時間は小規模作業で約215秒。設定ファイルにおける期待値であり、承認処理の遅延ではないため、本記事の2〜4秒と比較することはできない |
| 大規模なスループット | Google Cloudブログ、13万ノードのGKEクラスター(2025-11-22) | Podの作成とバインドが約1,000個/秒、39,000個のPodのプリエムプに93秒(ベンダーテスト) |
| Volcanoの規模 | Volcanoパフォーマンスチューニングガイド(2026-05-26更新) | Kind環境でPod 10,000個(1つ用件の作業10,000個)に約250秒、WebHook調整後に約180秒(計算上秒速約40個から56個)。WebHookのタイムアウト10秒でPod作成要求の98.7%が失敗したと記述 |
| DRAのステージ | Kubernetes機能ゲートドキュメント | コアDRAは安定(stable)ステージ。上記第7節の表 |
外部資料のうち、ギャングのデッドロックの発生頻度を直接測定したものは存在しない。『MLaaS in the Wild』(NSDI 2022)はPDFがサイズ制限を超えて開けなかったため、その数値は使用していない。
確認したバージョンと日付
2026-10-05時点のKubernetes 1.37.1、Kueue v0.20.0(2026-09-30)、Volcano v1.15.3(2026-09-30)、NVIDIA DRAドライバー v0.5.0(2026-08-19)である。筆者の実験環境はKubernetes 1.34.10、GPU Operator v26.3.3である。ギャングスケジューリングの実測は、別の使い捨てk3s v1.36.5で行った。
確認できなかったこと: ノード数が多い規模でのKueueとVolcanoの比較(テストは1ノード、作業数十個以下)、優先度が混在したキューの長期動作、KAI Schedulerの最新バージョンと成熟度、KueueとVolcanoを併用する構成のサポート範囲、NVIDIA DRAドライバーのGPU割り当て機能のステージ(READMEとドキュメントサイトで異なる)、Kueue TASとNVLinkラベルの具体的な連携、上記のコマンドの実際の出力である。容量の計算は説明用の算術であり、負荷テストに置き換える必要がある。