TT Lab
ブログ

한국어 English 日本語 中文

はじめる
ブログ

ブログ

空けておいた limit は無制限ではなく 0 だった: KAI-Scheduler のキューのドキュメントの PR が 13 日でマージされるまで

結果: マージ完了 (2026-11-09 17:44, kai-scheduler/KAI-Scheduler#2532), issue #2531 も一緒にクローズ。 実際には GitHub の PR・issue・レビュー・タイムライン・チェックの記録、KAI-Scheduler のソース (PR のベースコミット 0243c4e9) と API モジュール (タグ v0.2.0)、 コントリビューション原簿の実測原データと下書き、TT Lab リポジトリのコースのコミットから取得しました。時刻は韓国標準時 (KST) です。

目次

一言まとめ

PR kai-scheduler/KAI-Scheduler#2532 docs(queues): explain that an omitted limit is 0 and fix the example queues
変更内容 docs/queues/README.md 1 ファイル。特殊値の表の 1 行、説明の 1 段落、例のキューセットに不足している limit: -1 と memory のブロック。16 行追加、2 行削除
反応 他のコントリビューターによるコードパスの確認と 1 つの質問、コラボレーター権限を持つレビュアーによる承認 (後に自動解除)、メンテナーの承認と skip-changelog ラベル
結果 PR 公開 10-27 14:34 → マージ 11-09 17:44、約 13 日 3 時間 10 分。マージキューを経た squash マージ
적지 않은 limit 은 -1 이 아니라 0 으로 읽힌다 KAI-Scheduler v0.18.6 실측(10-27), 바탕 커밋 0243c4e9 의 소스 줄 ① 큐에서 limit 을 빼면 일어나는 일 limit 생략 CRD 기본값 없음 float64 의 0 resources.go L23 상한 0 -1 만 건너뜀 isOverLimit: 쓴 양 + 요청 ≤ 상한 을 자원마다 따로 본다 상한이 0 이면 그 자원을 조금이라도 청하는 파드는 OverLimit ② 문서 예시를 그대로 적용한 큐에 같은 파드 하나 파드: cpu 100m · memory 32Mi · GPU 1장 고치기 전 고친 뒤 Basic Pending · 메모리 0 GB Running Hierarchical Pending · CPU 0 cores Running Unlimited Pending · 메모리 0 GB Running PR: 표 한 줄과 설명 한 문단, 예시 셋에 limit -1 (+16 −2) 「Unlimited Queue - default value is -1」 제목도 고침 최종본(memory quota 0)으로 다시 돌린 기록은 없음: 미실측
図: 少なからぬ limit が 0 として読み取られ、ドキュメントの例のキューセットがすべて Pod を受け付けられなかった様子

1. 何が問題だったのか: 例のセットがすべて何も受け付けない

KAI-Scheduler は GPU ワークロードのための Kubernetes スケジューラーです。Pod に schedulerName: kai-scheduler を記述すると、デフォルトのスケジューラーの代わりにこのスケジューラーが 配置を引き受け、Pod が属するキュー (Queue) の割当量を考慮してリソースを割り当てます。キューには、リソース (cpu・memory・gpu) ごとに保証量 quota と上限 limit があります。

TT Lab の学習サイトの「Kubernetes GPU スケジューリングと AI バッチ」コースを作成していた 10-27 の未明に、この罠を最初に踏みました。実験用 VM に k3s を 1 台立ち上げ、 ダミーの GPU 4 基 (fake-gpu-operator 0.2.1) と KAI-Scheduler v0.18.6 をデプロイした後、gpu quota: 1 に limit を記述していないキューを作成して、GPU 1 基分の Pod を投入しました。クォータが 1 だから 1 基分は受け付けられるだろうと思ったのですが、Pod は Pending のまま留まり、PodGroup には次のような条件が付与されました。

OverLimit: nolimit quota has reached the allowable limit of GPUs. Limit is 0 GPUs,
currently 0 GPUs allocated and workload requested 1 GPUs.

limit を -1 に変更すると、待ち受けていた同じ Pod が配置されました (ハンズオン検証の記録 vfy-kai.log)。何秒で起動したかは計測していません。この出来事はコースのハンズオン 2 つ (「limit を空けたキューの罠」 、「limit に -1 と書いて直す」) となり、同日朝のコースのコミット (4cdcc0b3) のリーディング資料にも「limit は必ず -1 か数値を書いてください」と記載しました。

問題はドキュメントでした。キューのドキュメントの特殊値の表では、空けておいた limit を「追加リソースなし (デフォルト)」と説明しており、最後の例のタイトルは 「Unlimited Queue - default value is -1」です。読む側としては、空けておけばクォータまでは使用できるか、完全に無制限であると期待してしまいます。 そのため、ドキュメントの例のキューセットを 文字通りに 適用し、同じ Pod (requests: {cpu: 100m, memory: 32Mi}, limits: {nvidia.com/gpu: 1}) を 1 つずつ投入してみました。原データはコントリビューション原簿の kai-docs-test.log·kai-docs-after.log であり、Pod ごとに 15 秒間待機した後にステータスを読み取りました。

例のキュー 修正前 (ドキュメントのまま) 修正後 (PR の初版)
Basic Queue (memory なし) Pending, Limit is 0 GB (メモリ) Running
Hierarchical Queue (cpu・gpu に limit なし、 memory なし) Pending, Limit is 0 cores (CPU) Running
Unlimited Queue (memory なし) Pending, Limit is 0 GB (メモリ) Running

Basic Queue に memory: {quota: -1, limit: -1} を追加するだけで、待ち受けていた Pod が Running になりました。GPU の上限を記述した例も、「Unlimited」という 名前の例もメモリブロックがなかったため、メモリを少しでも要求する Pod を受け付けられなかったのです。Kubernetes でメモリを要求しない Pod は 稀であるため、このドキュメントを見て最初にキューを作成する人はほぼ確実にここでつまずきます。

環境は実験用 VM 1 台 (4 vCPU・4GiB)、k3s v1.36.5、KAI-Scheduler v0.18.6 であり、GPU はダミーです。本物の GPU ノードや複数ノードでは 計測していません (未計測)。ただし、この判定はスケジューラーがキューの数値のみを見て行う処理であるため、GPU が本物であるかどうかとは無関係であると考えられます。

2. 原因: 型の 0 と -1 のみをスキップするチェック

行番号は PR のベースコミット 0243c4e9 と、 そのコミットが使用する API モジュール kai-scheduler/api v0.2.0 を基準としています。

要約すると、コードはそのままで辻褄が合っています。「空ければ 0、0 ならブロック、-1 のみ無制限」です。狂っていたのはドキュメントの説明と例でした。 そのため、issue には 2 つの方向性を併記しました。ドキュメントを現在の動作に合わせるか、意図されたデフォルト値が -1 であるならば CRD のデフォルト値とするかアドミッションの段階で補完することです。 動作を変更するかどうかはメンテナーが決めることであるため、すぐにマージできるドキュメント側のみを PR として作成し、「デフォルト値を変更する方を選択する場合はこの PR はクローズしてもよい」と 記載しました。

3. 何を変更したのか: diff の要点

-| `limit` | `0` or unset | No additional resources allowed (default) |
+| `limit` | `0` or unset | No resources of this type can be allocated to the queue, including its quota (default) |
+
+Each resource (`cpu`, `memory`, `gpu`) is checked on its own. If a queue omits a resource or its `limit`,
+the limit for that resource is `0`, and workloads that request it stay pending with an `OverLimit` condition
+such as `Limit is 0 GB`. Set `limit: -1` for every resource you do not want to cap.
 # Basic Queue, Hierarchical Queue
+    memory:
+      quota: 0
+      limit: -1
 # Hierarchical Queue 의 cpu·gpu 에도
+      limit: -1
-### Unlimited Queue - default value is -1
+### Unlimited Queue
+`-1` is not filled in for you: set it on every resource.
 # Unlimited Queue
+    memory:
+      quota: -1                          # Unlimited quota
+      limit: -1                          # No limit

(説明の段落は元々 1 行だったものを読みやすく 3 行に分割して記載しました。)

PR の本文には、第 1 節の修正前後の表、実測環境、「ドキュメントのみを変更し、コード・チャートはそのまま」という旨、 skip-changelog ラベルの付与のお願い、同一ファイルの別の セクションを修正するオープンな PR #2275(MIG 計算の説明) が先にマージされた場合はリベースするという旨、 および AI の助けを借りて不一致を発見し、実測して下書きを作成した旨の告知を記載しました。

このリポジトリの CONTRIBUTING では DCO (Signed-off-by)、Conventional Commits 形式の PR タイトル、動作が変更される変更に対する変更履歴の断片 (ドキュメントのみの場合は skip-changelog ラベル) が求められ、外部コントリビューターの PR には「信頼されたレビュアー 2 名の承認」が求められます。AI の使用に関するリポジトリのルールは特に なかったため、上位財団の生成 AI ガイドラインを読み、事実通りに告知しました。外部コントリビューターにはラベルを付与する権限がないため、本文で依頼しました。

4. 反応と対応: 1 つの質問と消えた承認

時刻 日
10-27 未明 実験用 VM で罠を発見、ドキュメントの例のセットの修正前後を実測 (原データファイルの時刻 01:46・02:30・02:38)
10-27 14:33–14:34 issue #2531、42 秒後に PR 公開 (初版 c871f61b、SSH 署名)
10-27 19:28 他のコントリビューターによるレビュー (コメント): コードパスが新しい説明と一致していることの確認と 1 つの質問
10-28 21:39 コラボレーター権限を持つレビュアーが初版を承認
10-29 00:47 質問を反映したコミット 47bd30b5 を push、その瞬間に承認が自動解除。返信
10-29 00:54 2 つのコミットを 1 つの SSH 署名付きコミット (2c23833a) にまとめ、強制プッシュ
10-29 06:17 質問したコントリビューターが「2c23833a reads right」
11-01 01:21 解除された承認を再確認してほしいというコメント
11-09 16:04–16:05 メンテナーの承認、ズ skip-changelog ラベル
11-09 17:43–17:44 マージキューに入り 4 つのチェックを通過、17:44:50 にマージ (e5f0f298)、issue #2531 もクローズ

質問。 他のコントリビューターは proportion.go・CRD・isOverLimit を直接辿って「コードが新しい文章と一致している」ことを確認した上で、要求ではなく質問として 1 点尋ねました。新しい memory ブロックが quota: -1 であるためメモリの 保証量まで無制限になるが、GPU の例は「保証なし」を quota: 0 と 記述しているため、上限のみを解除する意図であれば quota: 0·limit: -1 の方が近いのではないかという指摘です。もっともな指摘でした。私たちが望んでいたのは 「上限を 0 にしないこと」であり、保証量を変更することではありませんでした。そのため、上限を記述した 2 つの例 (Basic・Hierarchical) のメモリを クォータである quota: 0·limit: -1 に変更し、すべてのリソースが無制限である Unlimited の例のみ quota: -1 としました。上記の diff がその最終版です。

この最終版を実験用 VM で再度実行した記録はありません (未計測)。第 1 節の「修正後」はメモリが quota: -1 であった初版の結果です。上限のチェックは limit のみを確認するため結果は同じになると考えられ、同じ実測において Unlimited の例の gpu が quota: 0·limit: -1 として GPU 1 基を受け付けたことがその根拠となりますが、 メモリの quota: 0 自体を再計測したわけではありません。

消えた承認。 コラボレーター権限を持つレビュアーが初版を承認してから 3 時間後に質問反映コミットを上げ、GitHub のタイムラインにはまさにその push によって 承認が解除 (dismissed) された記録が残りました。解除を引き起こしたコミットは 47bd30b5 です。このリポジトリは新しいコミットがアップロードされると以前の承認を無効にする設定を使用しているようです(リポジトリの設定を見る権限がないため未確認)。7分後、私たちは2つのコミットを署名付きコミット1つにまとめて再アップロードし、11-01に残した再承認をお願いするコメントには「squash pushが承認を消した」と書きました。イベントの記録を再び見ると、squashではなくその前のpushでした。どちらにせよ承認の後のpushは承認を消すという点はおなじですが、原因を語るときは記憶ではなくタイムラインを先に見るべきでした。

待機とマージ。 10-29の未明に質問が整理されてから11日間、新しいレビューはありませんでした。11-09 16:04にメンテナーが承認し、1分後にラベルを付け、17:43にマージキューに入れました。マージキューではPRのワークフロー、変更履歴の検査、PRタイトルの検査、DCOが14秒以内にすべて通過し、ドキュメントのみが変更されたPRであるためビルドとE2Eは「必要なし」としてスキップされました。マージコミットの作者は私たちのアカウントであり、メッセージはPRタイトルの末尾に (#2532) とDCO署名が付いたものです。

PRの画面には赤い検査が2つそのまま残っています。変更履歴の検査と承認の数を数える検査(Approval Gatekeeper)が10-29の強制更新時に実行されて失敗し、11-09 16:03に再び実行されましたが、承認とラベルが付く1〜2分前だったため再び失敗しました。誰が再実行したのかは公開記録から確認できませんでした。Approval Gatekeeperは外部コントリビューターのPRに対して組織のメンバーや協力者による2つの承認を求めますが、マージ時点で有効だった信頼された承認はメンテナーの1つでした(協力者の承認は解除されたままで、質問したコントリビューターは意見のみを残しました)。この検査はマージキューでは実行されなかったため、マージを妨げる必須の検査ではないようです。ルールの適用はメンテナーの判断であり、私たちが個別に対応すべき事柄ではありませんでした。

11-10の時点でこのコミットを含むリリースタグはありません。マージのあとに登場したv0.16.16やv0.14.12は古いバージョンの系統のパッチリリースであり、このドキュメントの変更はmainにのみ存在します。ドキュメントはGitHubのmainから直接読み込まれます。

5. 学んだこと:オープンソースコントリビューションのコツ

ティティの一言。 学校の売店のカードに「1日の限度額」の欄があったのですが、空欄にしておけば無制限だと思ったそうです。でも、売店のおばちゃんは空欄を0円として読み取りました。だから牛乳一本も買えませんでした!案内文には「空欄はデフォルト値」とだけ書かれていました。案内文に「空欄は0円です、無制限なら-1と必ず書いてください」を追加しようと言ったら、13日後に新しい案内文が貼り出されました。途中の落書きを一つ直したせいで、もらっていたスタンプが消されてしまったのはちょっと惜しかったですが。

リンク

すべての資料は2026-11-10に閲覧しました。

ログインするといいねできます

コメント

まだコメントはありません。

ログインするとコメントできます