- Published on
Ray 2.56のラベルロカリティスケジューリング — 配置グループがノードではなくNVLinkラックを見始めた
- Authors

- Name
- Youngju Kim
- @fjvbn20031
- はじめに — ノード境界はもう性能境界ではない
- 配置グループの復習 — 4つの戦略はすべてノード単位だ
- 問題 — NVLラックでSTRICT_PACKは不可能で、PACKは無頓着だ
- Ray 2.56の答え — ノード戦略の上にドメインレベルの層を載せる
- 障害セマンティクス — ドキュメントが正直に書いている部分
- アルファはアルファだ — 限界のリスト
- この機能が置かれた場所 — ラベルへ収束するRayのスケジューリング
- いつ使い、いつ無視していいか
- おわりに
- 参考資料
はじめに — ノード境界はもう性能境界ではない
分散スケジューラには、古くからの暗黙の前提が1つあります。「速いインターコネクトはノードの中で終わる」。8-GPUのHGXサーバーの時代には、この前提はおおむね真でした — NVLinkはノード内部でGPU同士を束ね、ノードとノードの間はInfiniBandやイーサネットに落ちます。だからスケジューラが「同じノードに寄せる」ことさえできれば、速い通信は自動的についてきました。
NVIDIAのラックスケールシステムがこの前提を壊しました。GB300 NVL72は、NVIDIAのスペック基準(ベンダー自己数値)でBlackwell Ultra GPU 72基とGrace CPU 36基を1つのラックの中で1つのNVLinkドメインとして束ね、NVLink帯域を130 TB/sと提示しています。つまり「速いインターコネクト」の境界は、もはやノードではなくラックです。ノード単位でしか考えないスケジューラには見えない境界です。
2026年6月29日に出たRay 2.56.0は、この地形変化に対するRayコアの最初の応答をリリースハイライトとして載せました — GPUドメインを認識する配置グループ、公式名称ではラベルロカリティスケジューリング(label locality scheduling)です。本稿では、この機能が何を解き、正確にどう動き、アルファの札が付いた理由は何かを、リリースノート・ドキュメント・ソースコードで確認できた内容から整理します。
配置グループの復習 — 4つの戦略はすべてノード単位だ
Rayの配置グループ(placement group)は、リソースバンドルの束を原子的に予約するギャングスケジューリングのプリミティブです。「GPU 4基・CPU 2基のバンドル18個を全部確保するか、さもなくば1つも取るな」といった要求を表現します。マルチノード学習や分散推論のように、ワーカーの一部だけが立ち上がっても意味がないワークロードの基本ツールであり、Ray Serveで実現するスケーラブルなLLMサービングパイプラインや分散学習スタックの下でもこれが回っています。
配置戦略は4つです — PACK(1ノードに寄せるのを選好)、STRICT_PACK(1ノードを強制)、SPREAD(ノードごとに分散を選好)、STRICT_SPREAD(ノードごとに分散を強制)。ここで注目すべき語は戦略名ではなく「ノード」です。4つの戦略すべてで、配置の単位はノードです。ノードより大きなトポロジー — ラック、NVLinkドメイン — は、この語彙に存在しません。
問題 — NVLラックでSTRICT_PACKは不可能で、PACKは無頓着だ
Ray 2.56.0のドキュメントが挙げる例をそのまま移すとこうです。ラック2つ、ラックごとにノード18台、ノードごとにGPU 4基・CPU 2基のクラスタで、バンドル18個を1つのラックの中に配置したい。
from ray.util.placement_group import placement_group
# 試み1: STRICT_PACK -> バンドル18個を「ノード1台」に入れようとする。
# ノードにはGPUが4基しかないので、この配置グループは永遠にpending。
pg = placement_group([{"GPU": 4, "CPU": 2}] * 18, strategy="STRICT_PACK")
# 試み2: PACK -> ノード群に分けて載せるが「ラック」という概念がないので、
# バンドルが2つのラックにまたがってもスケジューラは何の不満もない。
pg = placement_group([{"GPU": 4, "CPU": 2}] * 18, strategy="PACK")
2つのラックにまたがると何が起きるかというと — またがった地点の通信がNVLinkドメインの外、つまりラック間ネットワークに落ちます。ドメインの中にいるために買ったハードウェアを、スケジューラが半分無効化するようなものです。
もちろん回避策はありました。ノードごとにカスタムラベルを付け、静的なラベルセレクタで固定する方法です — たとえばすべてのバンドルにrack-1ラベルを要求する形で。ドキュメントはこの回避策の限界を2つ明示します。第一に、セレクタが静的なので障害に対応できません — rack-1のノードが全部死ぬと、配置グループは別のラックへ自動で移れません。第二に、本当に欲しいのは「どれでもいいから1つのラック」なのに、特定のラックを手で指定しなければならず、ラックが増えればこの管理がそのまま負担になります。
Ray 2.56の答え — ノード戦略の上にドメインレベルの層を載せる
ラベルロカリティスケジューリングは、既存のノード単位の戦略を置き換えるのではなく、その上にドメインレベルのスケジューリング層を1つ載せます。ドキュメント基準で、動作は3段階です。
- 候補ノードたちをray.io/gpu-domainラベルの値でまとめる — 同じ値なら同じドメイン。
- すべてのバンドルを収容できるドメインを1つ選ぶ。
- そのドメインの中で、ユーザーが指定したノードレベルの戦略をそのまま適用する。
ドメインレベルではSTRICT_PACKのみをサポートします — 配置グループ全体が1つのドメインの中に入るか、さもなくばスケジュールされません。発動条件は、配置グループのすべてのバンドルがbundle_label_selectorでGB200またはGB300のアクセラレータタイプを要求することです。
import ray
from ray.util.placement_group import placement_group
bundles = [{"GPU": 4, "CPU": 2}] * 18
label_selector = [{"ray.io/accelerator-type": "GB300"}] * 18
pg = placement_group(
bundles=bundles,
bundle_label_selector=label_selector,
)
ray.get(pg.ready()) # 18個のバンドル全部が同じray.io/gpu-domainの中に確保される
発動条件がどれほど狭いかは、2.56.0タグのソースでそのまま確認できます — トリガーは文字どおりGB200とGB300という2つの値のハードコードされた集合であり、バンドルが1つでもこのセレクタを付けているのに残りが同じ値でなければValueErrorを投げます。H100であれB200であれ、他のアクセラレータタイプを書けば、ドメイン層なしのごく普通の静的ラベルセレクタとしてしか動きません。
ここで運用者が必ず知っておくべき非対称が1つあります。2つのラベルの出所が違うのです。
- ray.io/accelerator-typeはRayが自動で付けます — ラベルのドキュメント基準で、デフォルトのノードラベルはray.io/node-idとray.io/accelerator-typeの2つであり、後者はNVMLが報告するGPU名から導かれます(CPUノードには付きません)。
- ray.io/gpu-domainはRayが自動では付けません。ドキュメントが明示的に書いている部分です — ノードがどのラックに属するかは運用者が埋めなければなりません。
# ノードが属するNVLinkドメイン(ラック)を自分でラベルとして指定しなければならない
ray start --labels="ray.io/gpu-domain=rack-1"
つまり「ドメイン認識」の半分はプロビジョニングパイプラインの仕事です。ノード起動の時点でラック識別子を取得してこのラベルに書き込む自動化がなければ、この機能は始まることすらできません。ちなみにオートスケーラ側は準備ができています — ドメイン制約とラベルセレクタをオートスケーラプロトコルへ伝える変更(#61614、#62487)が同じリリースに入っていて、スケールアップの判断もドメイン情報を受け取って行います。
障害セマンティクス — ドキュメントが正直に書いている部分
この機能の存在理由の半分は障害対応です(静的セレクタはラックが死ねばそこで終わりでしたから)。ドキュメントが明示したセマンティクスは2つに分かれます。
部分障害 — ドメインの中の一部のノードが死ぬと、Rayは失ったバンドルを同じドメインの生きているノードへ再スケジュールします。残ったバンドルの上のアクター・タスクは回り続けます。ただし、同じドメインの中にリソースが足りなければ、そのバンドルたちはinfeasible状態でキューに残り、同じドメインにリソースが空くまで待ちます。別のドメインへ移したいなら方法は1つだけです — remove_placement_groupで削除して新しく作ること。そしてこの呼び出しは、その配置グループのバンドルを使っていたアクターとタスクを全部強制終了し、再起動はしてくれません。復旧の自動化はユーザーの仕事です。
全体障害 — ドメインのノードが全部死ぬと、Rayがドメイン割り当てを消し、配置グループ全体を別のドメインへ再スケジュールします。静的ラベルセレクタができなかったのが、まさにこれです。
どこに確保されたかは確認できます。ダッシュボードの配置グループテーブルにLabel Domain列ができ(#62533)、状態APIでも見えます。
ray list placement-groups --detail
# 出力の一部 (2.56.0ドキュメントの例を要約)
- placement_group_id: 237f47c3235ac1a96ad423c3f74501000000
name: gpu-domain-pg
state: CREATED
label_domain_key: ray.io/gpu-domain
label_domain_assignments:
ray.io/gpu-domain: rack-2
ラベルロカリティを使わない配置グループでは、label_domain_keyが空文字列です。
アルファはアルファだ — 限界のリスト
ドキュメント自身がアルファだと警告する機能であり、制約が明確です。確認できたものだけを並べます。
- トリガーがGB200・GB300専用です。ソースにハードコードされた2つの値であり、ドキュメントもこの2つのアクセラレータタイプだけをサポートすると明示します。機能の出発点になったPR #61442(2026年4月3日マージ)も目的を隠しません — NVMLドメイン(GPUドメインとして抽象化)のためのものであり、まずはGB300ラックを狙うと書かれています。
- ドメインレベルの戦略はSTRICT_PACK 1つです。ドキュメントは今後のドメインレベル戦略の追加と任意のラベルドメイン対応の計画に触れていますが、現時点では計画にすぎません。
- ray.io/gpu-domainラベルは手動です。ラックトポロジーをラベルへ書き写す自動化を自前で作る必要があり、このラベルが間違っていればスケジューラは間違ったトポロジーを信じます。
- 部分障害時の同一ドメイン待機セマンティクスは両刃です。ドメイン内復旧が目標なら正しいデフォルトですが、「どのドメインでもいいから早く立ち上がること」の方が重要なワークロードなら、infeasibleキューで無期限に待つ方が損であり、脱出口はアクター全滅を覚悟した作り直しだけです。
- できたてのコードパスです。同じリリースサイクルで、ラベルドメイン配置グループがinfeasibleキューに閉じ込められるバグの修正(#62483、4月25日マージ)がすでに入っています。アルファの札は飾りではありません。
- 性能数値は公開されているものがありません。リリースノートにも、ドキュメントにも、PRにも、この機能のベンチマークはありません。そもそも速度最適化ではなく配置セマンティクスの機能であり、得の大きさは「ドメイン内NVLink帯域 対 ラック間ネットワーク帯域の差 × あなたのワークロードのノード間通信量」で決まります。その数字はあなたのクラスタで測るしかありません。
この機能が置かれた場所 — ラベルへ収束するRayのスケジューリング
ラベルロカリティは孤立した機能ではなく、Rayのスケジューリングがラベルセレクタの上へ再編されていく流れの最新段階です。確認できるタイムラインはこうです — ラベルスケジューリング自体は2.49(2025年8月)からベータ機能として入り(ドキュメントは「KubeRayからスケジューリングを制御する用途」と紹介しますが、ray start --labelsのような一般的な経路も併せてドキュメント化されています)、2.50からはオートスケーラv2を有効にした動的クラスタなら、ラベル要求を満たすために指定されたワーカーグループでノードを増やせるようになり、2.53はRay TrainのScalingConfigにlabel_selectorを付けました。そして2.56は、Ray内部のライブラリ群が使っていたNodeAffinitySchedulingStrategy(soft=False)をray.io/node-idラベルセレクタへ置き換える作業(#54940)とともに、ラベルの上にドメインという概念を載せる今回の機能を載せました。特殊目的のスケジューリング戦略を1つずつラベルの語彙へ吸収していく方向です。TPU側は同じ問題をSlicePlacementGroupという別経路で解いてきましたが、今回のリリースでここにも明示的なbundle_label_selectorのサポートが付きました(#63171)。
この方向そのものはRayだけのものではありません。Kubernetesもv1.36のWorkload・PodGroup APIでギャングスケジューリングとトポロジー認識をコアスケジューラへ引き入れている最中です。ラックスケールのハードウェアがスケジューラたちに同じ宿題を出していて、みな「ノードより大きな単位」という答案を書き始めた、というわけです。
いつ使い、いつ無視していいか
使う理由がある場合の条件は明確です — GB200・GB300のNVLラックがあり、クラスタにドメインが2つ以上あり、配置グループ1つが複数ノードにまたがるワークロード(マルチノード学習、ノードを越えるテンソル/エキスパート並列推論)を回すとき。このとき、この機能がくれるものは2つです。ドメインまたぎの防止、そしてドメインを手で指定しなくてよいこと(全体障害時の自動引っ越しを含む)。
逆に、次の場合は無視していいです。
- ワークロードが1ノードに収まる — STRICT_PACKがすでに答えです。
- マルチノードのNVLinkドメインがない — 8-GPUサーバーのクラスタではノード境界がそのままドメイン境界なので既存の戦略で十分ですし、そもそもGB200・GB300でなければトリガーが発動しません。
- クラスタ全体が1つのドメインだ — 選ぶドメインが1つしかなければ、ドメイン選択層にできることはありません。
- 障害時に「同一ドメイン待機」より「どこでもいいから早く再起動」が重要だ — この機能の部分障害セマンティクスがかえって足を引っ張りかねません。作り直しの自動化を備えて使うか、今はまだ距離を置く方がよいです。
おわりに
整理するとこうです。Rayの配置グループはこれまでノード単位の語彙しか持っておらず、NVL72系のラックスケールシステムは、その語彙では表現できない境界 — マルチノードのNVLinkドメイン — を作りました。Ray 2.56.0のラベルロカリティスケジューリングは、同じray.io/gpu-domainラベルを持つノードの塊をドメインと定義し、配置グループ全体を1つのドメインへSTRICT_PACKする層をノード戦略の上に載せます。静的ラベルセレクタと違い、ドメイン全体の障害時には自動で別のドメインへ移ります。
同時に、この機能はアルファの定義に忠実です。GB200・GB300のハードコードトリガー、ドメインレベルSTRICT_PACKという単一戦略、手動のgpu-domainラベル、破壊的なドメイン強制移動の経路、そして公開された性能数値なし。NVLラックを実際に運用しているなら今から検証を始める価値がありますし、そうでないなら — この機能があなたにとって意味を持つ日は、ハードウェアの発注書が先に教えてくれるでしょう。
参考資料
- Ray 2.56.0 リリースノート (2026-06-29)
- Ray 2.56.0 ドキュメント — 配置グループのラベルロカリティスケジューリングのセクション
- Ray 2.56.0 ドキュメント — ラベルベースのスケジューリング
- PR #61442 — GPUドメイン認識の配置グループ (Label Locality 1/n)
- ray-2.56.0タグのplacement_group.py — GB200/GB300トリガーの検証ロジック
- NVIDIA GB300 NVL72 製品ページ — ベンダースペック
- Kubernetes v1.36のWorkload/PodGroup API — 同じ問題へのK8s側の答え(関連記事)