openai4s/orchestration/slurm/Slurm backend——全树唯一被允许叫出调度器名字的地方。INV-2 主张的是编排核心的洁净,而不是"任何地方的代码都不许知道作业怎么提交":总得有人知道。把这份知识收进一个目录,正是这条规则可被检查的原因:泄漏守卫按路径跳过这里,于是这里之外任何带调度器形状的东西都是缺陷,而不是见仁见智。
| 文件 | 是什么 |
|---|---|
__init__.py |
重导出 backend、broker 与 cluster 配置。由组装代码 import;编排核心永不触达。 |
backend.py |
AllocationBackend 实现,以及把 TIMEOUT 翻成 (FAILED, TIME_LIMIT_EXCEEDED) 的状态表。三条行为是承重的而非顺带的。丢失的提交是 Unknown 而非失败——并且 submit 开头就先问"这个 token 是不是已经在外面了",所以 Unknown 之后的重试返回 Existing,而不是往集群上再放一个作业(INV-8)。这次查找同时匹配 JobName 与 Comment:sacct 只有在站点开启 AccountingStoreFlags=job_comment 时才持久化 Comment,而 JobName 是普通账务字段;查找本身得不到答案时,submit 返回 Unknown 而不是继续往下走——没有答案不等于答案是"没有"。队列与账务里都找不到的作业是 LOST 而非 COMPLETED:为找不到的工作宣称成功,是唯一一种会悄悄弄丢科研成果的答案。而联系不上的调度器不改变 phase——一次集群故障不该终结所有人的作业。多节点 SESSION 以每节点一个 srun task 的方式拉起,每个 worker 从 SLURM_PROCID 解析自己的 rank,于是 gang 就绪在每个节点上都有 worker,而凭据值不进作业环境。run_task 在工作负载已经持有的 allocation 里跑步骤,没有存活句柄时直接拒绝——那个诱人的回退(再提交一个 allocation)正是 INV-4 禁止的行为。 |
broker.py |
子进程边界:所有 sbatch/squeue/sacct/scancel 调用与全部解析都在这儿,好让 INV-9 在一个地方可查。argv 是列表且 shell=False,所以名为 gpu; rm -rf / 的 profile 是一个参数而不是一条命令;job name 与 comment 另有字符类限制,因为正是这两个字段把我们自己的标识符送进调度器输出、再从解析器里取回来。带凭据形状的环境变量一律拒绝——秘密以 0600 文件的路径形式传递。环境变量的值和名字一样要校验:--export 是不带转义的逗号分隔列表,所以含逗号、换行或 NUL 的值——以及读起来是 ALL/NONE/NIL 的值——都会被拒绝,赶在 {"X": "1,ALL"} 把 daemon 的整个环境(连同凭据)传播进作业之前。StepSpec 让 allocation 内的 srun 步骤守同一套纪律,长时间步骤的输出在字节上限下被排空,而不受控制命令超时的约束,免得传输层杀掉它本该观察的那个步骤。SlurmCommandError 区分 timed_out、unreachable 与普通拒绝;写测试时证明了为什么必须区分:把"PATH 上没有调度器"吞成"没有这个作业",会把所有在跑的作业判成丢失。 |
profiles.py |
cluster.toml:把科研人员说得出的 profile 名(gpu-interactive)映射到管理员掌控的 partition 与 QoS 的唯一一个文件(D5)。ClusterProfile.public() 是给 admin 看的视图,刻意不含这两者——JSON body 里的队列名只是走得慢一点的同一种泄漏。有 tomllib 就用它,没有就用一个刻意做小的回退:requires-python 是 3.10、CI 也跑 3.10,只认 tomllib 会让整个集群功能在下限版本上直接死掉。回退只认表、字符串、整数与布尔,其余一律带行号拒绝而不是猜——读不出来还能补救,读错了就是排到错误的队列上。在它认的范围内则与 tomllib 严格对齐:带引号的点号键仍是一个键,basic string 的转义序列与非 TOML 的整数写法一律拒绝而不是另作解释——两个读取器都是生产路径,一旦分叉,就是某个 profile 带着 configured: true 悄悄落到错误的队列上。未知键按名字拒绝而不是忽略,profile 还可以声明 nodes 来支持多节点 gang。 |