TT Lab
はじめる
学ぶ 学習パス コース

GPU Operatorとタイムスライシング

共有の設定 — スライス・MIG・クォータで分けておく

TT Labで続きを見る

目標

1枚を複数人で使えるようにする3つの方式を、設定とオブジェクトで扱います。スライス数の分だけ枠が増えることをノードのstatusで確認し、MIGノードはリソース名そのものが違うことをスケジューリングで確認し、チームに割り当てた分をクォータで固定するところまで行います。

なぜ重要なのか

GPUは、買ったあとに、使用率が1日平均で1桁なのに、待ち行列はいつも埋まっています。Pod1つがカード1枚を丸ごと確保するからです。ここから「1枚を複数人で使えるようにしてほしい」という要求が出てきて、答えは3つあるのに、名前が似ているので、よく混同されます。

最も重要な区別は、何を分けるのかです。タイムスライシングのreplicasは、GPUを分割しろという意味ではなく、同じデバイスを複数回登録しろという意味です。枠は増えますが、VRAMは分割されていない1つの塊なので、1つのPodが多く確保すると、残りがメモリ不足に遭います。MPSは、カーネルを同時に実行させてスループットを上げますが、分離は依然として弱いです。MIGだけがハードウェアで分けるので、メモリもエラーも分離されます。そして、MIGノードは、リソース名そのものがプロファイル名に変わるため、古いマニフェストがそのノードをずっと使えないことが起きます。

環境

このPodには、GPUもデバイスプラグインもありません。代わりに、kwokが起動した本物のコントロールプレーンがあり、ノードのstatusに数字を書くと、本物のスケジューラーがそれを見て判定します。クォータも本物のapiserverが検査します。作業ディレクトリは/root/gpushareで、その下のk8s/・bin/・out/を使います。

ステップ

  1. gpu-share・team-aネームスペースと、設定を2セット入れたConfigMapを作成します。
  2. ノードのラベルで設定を選び、アドバタイズ量をそれぞれ書きます。
  3. lab-node-2をMIG mixedノードにします。
  4. リソース名だけが違うPodを2つ起動して、行き先が分かれることを確認します。
  5. team-aにクォータを設定し、超過する要求が拒否されることをout/quota-denied.txtに残します。
  6. bin/check-request.shで、スライスを複数要求するリクエストを事前に弾きます。
  7. ConfigMapにmpsを追加し、out/isolation.txtに分離の表を書きます。
  8. 3つのワークロードを、それぞれに合うノードに配置して、out/placement.txtに書きます。

参考

デバイスプラグインの設定に名前を付けて、2セット入れる

gpu-shareとteam-aネームスペースを作成し、/root/gpushare/k8s/device-plugin-configs.yamlで、gpu-shareにdevice-plugin-configsというConfigMapを作成してください。キーはdefaultとsharedの2つで、sharedには、sharing.timeSlicingの下にfailRequestsGreaterThanOne: trueと、resources[0]がnvidia.com/gpu・replicas: 4で入ります。defaultにはsharingを置かないでください。

デバイスプラグインは、設定を1つのConfigMapに複数セット入れておいて、ノードごとに選んで使わせることができます。そうすれば、同じクラスターの中で、あるノードは丸ごと、あるノードは分け合って使えます。failRequestsGreaterThanOneは、スライスを2つ以上ほしいという要求を拒否するスイッチです。スライスを2つ受け取っても、同じデバイスを2回予約しているだけで、性能もメモリも2倍にならないからです。ConfigMapの値は複数行の文字列なので、|-を使います。

ノードのラベルで、どの設定を使うかを選ぶ

lab-node-0にnvidia.com/device-plugin.config=defaultを、lab-node-1に=sharedを付けてください。そのあと、2台のノードがアドバタイズするnvidia.com/gpuをstatusに書きます。2台とも物理GPUは2枚として、lab-node-0は2、lab-node-1は2にスライス数を掛けた値です。

デバイスプラグインは、ノードのnvidia.com/device-plugin.configラベルを見て、ConfigMapのどのキーを使うかを選びます。そのため、同じクラスターの中で、ノードごとに異なる共有ポリシーを置けます。アドバタイズ量は、推測せずにステップ1のreplicasをそのまま掛けて入れてください。capacityとallocatableの両方に書く必要があります。ここで増えるのは枠の数だけで、VRAMは依然として1つの塊です。

MIG mixedノードは、リソース名そのものが異なる

lab-node-2にnvidia.com/mig.strategy=mixedとnvidia.com/gpu.productラベルを付け、statusにnvidia.com/mig-1g.10gbを7としてアドバタイズしてください。このノードには、nvidia.com/gpuを書かないでください。

MIGは、ハードウェアでカードを分割します。mixed戦略では、デバイスプラグインがプロファイルごとに異なる名前でリソースをアドバタイズするので、Podが要求するリソース名も、nvidia.com/gpuではなく、nvidia.com/mig-1g.10gbのようなプロファイル名になります。そのため、このノードはnvidia.com/gpuをまったくアドバタイズしません。A100 40GBを1g.10gbで分割すると、7つのスライスができます。

リソース名が違うと、互いの枠には行けない

/root/gpushare/k8s/mig-job.yamlにPodを2つ書いて、gpu-shareに適用してください。mig-jobはnvidia.com/mig-1g.10gbを1個、plain-gpuはnvidia.com/gpuを1個要求します。どちらもノードの条件は指定しないでください。

条件を指定しなくても、リソース名だけで行き先が分かれます。mig-jobは、その名前をアドバタイズするノードにだけ行け、plain-gpuは、MIGノードには行けません。同じ物理カードを使っていても、スケジューラーにとってはまったく別のリソースです。そのため、MIGノードを導入するときに古いマニフェストをそのままにしておくと、Podがそのノードをずっと使えないことが起きます。

チームに割り当てた分を、クォータで固定する

/root/gpushare/k8s/gpu-quota.yamlで、team-aにgpu-quotaというResourceQuotaを作成してください。requests.nvidia.com/gpuを2に制限します。そのあと、GPUを1枚ずつ使うPodを2つteam-aに起動して上限を埋め、さらに1枚を要求する/root/gpushare/k8s/team-over.yamlを適用して、拒否メッセージを/root/gpushare/out/quota-denied.txtに保存してください。

拡張リソースも、ネームスペースのクォータで制限できます。キー名がrequests.<자원이름>の形である点だけが違います(プレースホルダーはリソース名です)。Pod2つは、nvidia.com/device-plugin.config: sharedのノードセレクターでスライスのノードに置くと、枠が十分にあります。上限を埋めたあとの要求は、スケジューラーではなくアドミッションの段階で拒否されるので、Podはそもそも作成されません。メッセージは標準エラー出力に出るので、2>&1で受けてください。

スライスを複数要求するリクエストを、デプロイ前に弾く

/root/gpushare/bin/check-request.sh <파드매니페스트>を作成してください(プレースホルダーはPodのマニフェストです)。コンテナがnvidia.com/gpuを1より大きく要求していたら、OVER=<컨테이너이름>=<개수>を1行ずつ出力して、終了コード1で終わります(プレースホルダーはコンテナ名と個数です)。そうでなければOKを出力して、0で終わります。GPUをまったく要求しないPodも、通過になる必要があります。

failRequestsGreaterThanOneは、デバイスプラグインが実行時に行う判定です。同じ判定をデプロイ前に行っておけば、ユーザーがリソースを誤解したまま計画を立てるのを防げます。複数のドキュメントが入ったファイルもあるので、yaml.safe_load_allで読み、limitsとrequestsの両方を見てください。採点ツールは、このスクリプトを3種類のマニフェストで実際に実行します。

3つ目の方式を追加して、分離の表を作成する

ConfigMapのdevice-plugin-configsにmpsキーを追加してください。sharing.mps.resources[0]がnvidia.com/gpu・replicas: 4で、timeSlicingは一緒に置きません。そして/root/gpushare/out/isolation.txtに、5行で分離の表を書いてください。TIMESLICING_MEMORY_ISOLATION、MPS_MEMORY_ISOLATION、MIG_MEMORY_ISOLATION、TIMESLICING_FAULT_ISOLATION、MIG_FAULT_ISOLATIONです。

MPSは、複数のプロセスのカーネルを1つのコンテキストにまとめて、本当に同時に実行させます。プロセスごとにメモリの上限をかけることもできますが、それは協調的な制限に近く、1つのプロセスが落ちるときに、他のプロセスまで影響を受けることがあります。5行の値は、yes・no・partialのどれかです。値が完全な分離ならyes、なければno、上限はかけられるが強制的な分離ではなければpartialです。

性質が異なる3つのワークロードを、それぞれに合うノードに配置する

/root/gpushare/k8s/placement.yamlで、gpu-shareにPodを3つ作成してください。prod-inferenceはMIGノードでプロファイルのリソースを、notebookはスライスのノードでnvidia.com/gpuを、batch-trainは分割しないノードでnvidia.com/gpuを、それぞれ1個ずつ使います。ノードはラベルで選んでください。そのあと、実際にどこに配置されたかを、/root/gpushare/out/placement.txtに<파드이름>=<노드이름>の3行で書きます(プレースホルダーはPod名とノード名です)。

基準線はこうです。分離が必要ならMIG、利用率が目的ならタイムスライシングです。本番の推論は、隣のPodのメモリ障害に巻き込まれてはいけないので、ハードウェアパーティションのほうへ送り、実験用のノートブックは、使用率が低いのでスライスのノードへ送ります。バッチ学習は、カードを丸ごと使うほうが速いので、分割しないノードへ送ります。ノードの条件は、ステップ2とステップ3で付けたラベルをそのまま使えばよいです。ファイルのノード名は、推測せずにkubectl get pod -o wideで確認して書いてください。