TT Lab
博客

한국어 English 日本語 中文

开始
博客

博客

留空的 limit 并不是无限制而是 0:KAI-Scheduler 队列文档 PR 历时 13 天合并始末

结果:已合并(2026-11-09 17:44, kai-scheduler/KAI-Scheduler#2532),Issue #2531 也一并关闭。 事实上,本文内容取自 GitHub 的 PR、Issue、Review、时间线、检查记录,KAI-Scheduler 源码(PR 的基础 Commit 0243c4e9)以及 API 模块(标签 v0.2.0), 贡献账本的实测原始数据与初稿,以及 TT Lab 仓库的课程 Commit。时间均为韩国时间。

目录

一句话总结

PR kai-scheduler/KAI-Scheduler#2532 docs(queues): explain that an omitted limit is 0 and fix the example queues
修改内容 docs/queues/README.md 一个文件。特殊值表格的一行、说明的一个段落,示例队列集中缺失的 limit: -1 和 memory 块。新增 16 行,删除 2 行
反应 其他贡献者确认代码路径并提出一个问题、协作者权限的审阅者批准(后自动解除)、维护者批准以及 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 。

在 10-27 凌晨制作 TT Lab 学习网站的“Kubernetes GPU 调度与 AI 部署”课程时,我首次踩到了这个坑。在实验 VM 上运行一台 k3s, 部署了 4 张虚拟 GPU(fake-gpu-operator 0.2.1)和 KAI-Scheduler v0.18.6,然后创建了一个未在 gpu quota: 1 中写入 limit 的队列,并放入了一个单 GPU 的 Pod。由于配额(Quota)为 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 改为 vfy-kai.log后,一直等待的同一个 Pod 随即完成调度(实操验证记录4cdcc0b3)。我没有计算具体几秒钟启动。这件事成为了课程的两个实操(“limit 留空的队列陷阱”与 “将 limit 写入 -1 来修复”),并在当天的课程 Commit( -1 )的阅读材料中写道:“请务必将 limit 写作

或具体数字”。 真正的问题在于文档。队列文档的特殊值表格将留空的 limit 解释为“无额外资源(默认值)”,最后一个示例的标题则是 “Unlimited Queue - default value is -1”。阅读文档的人自然会期待:留空的话要么能用到配额上限,要么干脆就是无限制。 于是,我们将文档中的示例队列集 原封不动地requests: {cpu: 100m, memory: 32Mi}, limits: {nvidia.com/gpu: 1}应用,并逐个放入相同的 Pod( kai-docs-test.log·kai-docs-after.log )。原始数据取自贡献账本的

,每个 Pod 等待 15 秒后读取状态。 示例队列 修改前(照搬文档)
修改后(PR 初版)memory Basic Queue(无 ) Limit is 0 GB Pending, (内存)
Running limit Hierarchical Queue(cpu·gpu 无 memory ,无 ) Limit is 0 cores Pending, (CPU)
Runningmemory Unlimited Queue(无 ) Limit is 0 GB Pending, (内存)

Running memory: {quota: -1, limit: -1} 仅在 Basic Queue 中加入

,等待中的 Pod 便转为了 Running。无论是设定了 GPU 上限的示例,还是名为“Unlimited”的示例,都因为缺少内存块,而无法接收哪怕请求一丝内存的 Pod。在 Kubernetes 中,不请求内存的 Pod 凤毛麟角,因此初次参考该文档创建队列的人几乎必然会在此处碰壁。

环境为单台实验 VM(4 vCPU·4GiB)、k3s v1.36.5、KAI-Scheduler v0.18.6,且 GPU 为虚拟设备。未在真实的 GPU 节点或多节点环境中进行测量(未实测)。不过,由于该判定是由调度器仅根据队列数字完成的,因此似乎与 GPU 是否真实无关。

2. 根本原因:仅跳过类型中 0 和 -1 的检查 0243c4e9 行号基于 PR 的基础 Commit kai-scheduler/api 及该 Commit 使用的 API 模块

会针对每种资源分别遍历,只有当上限精确等于 -1 时才会跳过(第 54 行)。除此之外,它都会检查“已用量 + 请求量”是否小于等于上限。如果上限为 0,那么只要请求哪怕一点该资源就会超出限制。由于请求量为 0 的资源会被跳过(第 58–60 行),因此如果是未请求内存的 Pod,即使没有内存块也能通过。 总结来说,代码本身逻辑前后一致:“留空则为 0,为 0 则阻断,仅 -1 代表无限制”。出问题的是文档的说明与示例。 因此,我在 Issue 中同时提出了两种方向:要么将文档修改为符合当前实际行为,要么如果预期的默认值是 -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

(说明段落原本只有长长的一行,为了便于阅读我将其拆分成了三行。)

PR 正文中包含了第 1 节的修改前后表格、实测环境、“仅修改文档,代码与 Chart 保持不变”的声明、 skip-changelog 标签请求、若修改同一文件其他部分的开放 PR #2275(MIG 计算说明)率先合并则会进行 Rebase 的说明,以及在 AI 辅助下发现不一致、进行实测并撰写初稿的声明。

该仓库的 CONTRIBUTING 要求满足 DCO(Signed-off-by)、Conventional Commits 风格的 PR 标题、会改变行为的变更对应的变更日志片段(如果是纯文档则为 skip-changelog 标签),并且要求外部贡献者的 PR 须获得“两名受信审阅者的批准”。由于仓库没有关于使用 AI 的单独规则,我阅读了上级基金会的生成式 AI 指南,并做出了如实申报。外部贡献者无权添加标签,因此我在正文中进行了请求。

4. 反应与应对:一个提问与被清除的批准

时间 日期
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 其他贡献者的 Review(意见):确认代码路径与新说明相符并提出一个问题
10-28 21:39 具有协作者权限的审阅者批准了初版
10-29 00:47 推送反映了提问的 Commit 47bd30b5 ,批准随即自动解除。回复评论
10-29 00:54 将两个 Commit 合并为一个带有 SSH 签名的 Commit(2c23833a)并进行强制更新(Force Push)
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 进入合并队列通过四项检查,17:44:50 合并(e5f0f298),Issue #2531 也随之关闭

提问。 另一位贡献者直接追踪了 proportion.go·CRD·isOverLimit ,确认“代码与新语句相符”后,指出这并非要求而是提问:由于新的 memory 块是 quota: -1 ,连内存 保证量也变成了无限制,但 GPU 示例将“无保证”写作了 quota: 0 ,因此如果意图仅仅是解除上限,使用 quota: 0·limit: -1 是否更加贴切?这是一个非常中肯的指出。我们想要的本来是“不要将上限设为 0”,而不是修改保证量。因此,我将设定了上限的两个示例(Basic·Hierarchical)的内存改为 quota: 0·limit: -1 ,仅保留所有资源皆无限制的 Unlimited 示例使用 quota: -1 。上方的 diff 即为最终版本。

并没有在实验 VM 中重新运行此最终版本的记录(未实测)。第 1 节的“修改后”是内存为 quota: -1 的初版结果。由于上限检查只看 limit ,推测结果应无二致,且同一实测中 Unlimited 示例的 gpu 以 quota: 0·limit: -1 获得了 1 张 GPU 即为依据,但并未单独测量内存 quota: 0 本身。

被清除的批准。 具有协作者权限的审阅者批准初版三小时后,我提交了反映提问的 Commit,GitHub 的时间线中随即便留下了因该推送而导致批准被解除(dismissed)的记录。引发解除的 Commit 是 47bd30b5 。。该仓库似乎使用了当有新提交上传时便会作废之前批准的设置(仓库设置无权查看,故未确认)。在 7 分钟后,我们将两个提交合并为一个带签名的提交并重新上传,而在 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 页面上,两个红色的检查依然保留着。变更记录检查和计算批准数的检查(Approval Gatekeeper)在 10-29 强制刷新时运行并失败了,虽然在 11-09 16:03 再次运行,但由于是在批准和标签贴上之前的 1~2 分钟,因此又失败了。谁重新运行的,无法从公开记录中确认。Approval Gatekeeper 要求外部贡献者的 PR 必须获得组织成员或协作者两人的批准,但在合并时依然有效的受信任批准只有维护者的一票(协作者的批准处于解除状态,提问的贡献者只留下了意见)。该检查并未在合并队列中运行,因此它似乎并不是阻止合并的必选检查。规则的应用由维护者判断,我们无需单独进行应对。

截至 11-10,没有包含此提交的发布标签。合并后发布的 v0.16.16 和 v0.14.12 是旧版本分支的补丁发布,此文档修改仅存在于 main 分支中。该文档可直接在 GitHub 的 main 分支中读取。

5. 学到的经验:开源贡献要点

Titi 的话。 学校小卖部的卡上有一个「每日限额」栏,我以为留空就是无限呢。但是小卖部阿姨把空白处读作了 0 元。所以连一瓶牛奶都买不到!说明上明明只写着「空白处为默认值」。当我建议在说明上加上「空白处是 0 元,如果是无限请务必填写 -1」之后,13 天后贴出了新的说明。中间因为修改了一个涂鸦而好不容易获得的盖章被清除了,觉得稍微有点可惜。

链接

所有资料均查阅于 2026-11-10。

登录后即可点赞

评论

还没有评论。

登录后即可发表评论