HPC (SLURM)
每个本地处理器都可以把工作提交为 SLURM 作业,而不是在调用 submit() 的进程内运行。加上 --hpc-mode 即可,工作流的其余部分完全不变。
这不仅仅是为了更快。本地运行依附于调用进程的后台线程,进程结束时它也随之结束 — 一次断开的 SSH 会话就能带走几个小时的地理编码结果。在 HPC 模式下,作业归调度器管理。
管理器(manager)模型
提交时并不会一次性把所有作业排入队列。每个工作单元生成一个子 sbatch 脚本,由一个轻量的管理器作业以滑动窗口方式调度:
manager(1 核、1 GB、其分区允许的最长墙钟时间)
├── child 0 ┐
├── child 1 ├── 同时最多 --worker 个在运行
├── child 2 ┘
└── … 有空位就补充
管理器轮询 squeue,每完成一个就提交下一个。它在结束时写入该阶段的状态标记,因此 refresh 对 HPC 运行和本地运行使用完全相同的一套状态机制。
对于必须按顺序执行的阶段,每个阶段的管理器在自己成功后再提交下一个,而不是一开始就用 --dependency=afterok 把所有管理器排进队列。预先提交会让每个尚未运行的管理器都以独立作业的形式占用账户的"已提交作业数"配额,却什么也不做。
--worker
一个参数,同时控制两个维度:
- 可以横向展开的阶段 → 8 个并发 SLURM 作业
- 无法展开的阶段 → 单个作业内的 8 个线程
第二种情况是真实存在的:phase_link 模式下 ISCE3_Burst 的 ifg 是单个进程,因为相位链接估计需要所有日期的完整 N×N 协方差矩阵,不存在可拆分的"逐干涉对"单元。若只设置作业数,该阶段会继续使用配置文件里保存的线程数运行,而 SLURM 却预留了它根本用不到的核心。
需要区别设置时可以分别覆盖:
当单作业阶段的 cpus_per_task 与 max_workers 不一致时,submit 会给出提示,并说明有多少核心空闲或被超额占用。
队列上限
集群会限制单个用户已提交的作业数,这与允许同时运行的数量是两回事。submit 会从分区的 QOS 读取该上限,并把 --worker 收敛到 上限 - 2,同时打印黄色警告:
--worker 40 exceeds what partition 'all' allows: its QOS 'jobmanagement' caps
you at 20 SUBMITTED jobs. Reducing to 18 ...
之所以预留两个名额而不是一个:管理器本身占一个;另外在链式交接的瞬间,正在结束的管理器和它提交的下一个管理器会短暂同时存在。
该上限统计的是你全部已排队的作业,不只是本次运行 — 如果还有其他作业在排队,请进一步调小 --worker。即使仍然触及上限,管理器也会把它当作背压处理,每 30 秒重试一次、最多两小时,而不是直接放弃整个阶段。
sbatch_options.json
首次提交时写入工作目录,然后停下来让你检查。该文件自带说明:_options 段列出每个可设置的键、它的作用和示例;_stages(或 _steps)说明每个单元是什么、会展开成多少个作业。
{
"default": { "time": "02:00:00", "cpus_per_task": 4, "mem": "16G",
"partition": "all" },
"manager": { "partition": "all" },
"cslc": { "time": "04:00:00", "cpus_per_task": 4, "mem": "32G" }
}
default 是每个阶段都会继承的基础配置,而不是"阶段缺失时才使用"的兜底。集群级别的设置 — partition、account、qos — 在这里写一次即可;每个阶段只覆盖自己真正需要的 time/cpus/mem。
manager 是特例:只读取 partition。管理器的核心数、内存和墙钟时间都是固定的,因为它只做记账工作,唯一的要求是活得比它监管的所有子作业都久。如果阶段耗时长或作业数多,请给它选择墙钟时间上限最长的分区。
以 _ 开头的键是文档,会被忽略。任何不属于已识别 SLURM 字段的键会被静默丢弃、不会传给 sbatch,因此拼写错误不会报错 — 可接受的键请查阅 _options。
各处理器的拆分粒度
拆分粒度取决于一个单元是否是独立进程。在单进程内已经多线程并行的工作,只会得到一个按线程数配置资源的作业。
| 阶段 | 作业数 | 单元 |
|---|---|---|
dem、tec |
各 1 个 | 受网络限制,只需几分钟 |
cslc |
每个 burst-日期一个 | 各自是独立的 COMPASS 进程 — 耗时主体 |
static |
每个 burst 一个 + 1 个归约 | s1_static_layers.py,随后拼接 LOS 图层 |
crop |
每个 burst 一个 | |
ifg |
1 | 相位链接需要完整协方差,不存在逐干涉对的单元 |
stitch、filt |
每个干涉对一个 | |
unwrap |
1 个准备 + 每个干涉对一个 | 准备阶段先生成水体掩膜,避免 N 个作业同时读写 |
作业清单来自 ifg_manifest.json 的阶段,在 ifg 运行前无法枚举。整条链一起提交没有问题 — 这些阶段会被标记为"推迟",并给出条件满足后应执行的确切命令。
NISAR GSLC 已经完成地理编码,因此 COMPASS 阶段(dem、tec、cslc、static)被跳过——只运行三个阶段:
| 阶段 | 作业数 | 单元 |
|---|---|---|
crop |
每个 GSLC 日期一个 | 将每帧裁剪到 AOI(一次廉价的 VRT gdal_translate) |
ifg |
1 | 相位链接需要整个协方差,没有逐对单元(对整个堆叠的单次 wrapped_phase.run) |
stitch |
每个干涉对一个 | |
unwrap |
1 个准备 + 每个干涉对一个 | 准备阶段先生成水体掩膜,避免 N 个作业同时读写 |
与 ISCE3_Burst 相同的推迟规则:逐对阶段的清单来自 ifg_manifest.json,在 ifg 运行前会被标记为推迟。
p2p 模式(stack_mode=False,默认)是每个干涉对一个作业。干涉对之间完全独立 — 多子条带模式下各自拥有独立的 case 目录,单子条带模式的输出以 intf/<julian_pair>/ 命名隔离 — 因此由单个管理器展开全部干涉对,完全不需要链式提交。
每个作业运行该干涉对的完整流程:配准 → 干涉图 → 滤波 → 解缠 → 地理编码。
stack_mode 则是按顺序执行的阶段:align → topo → intf → mergeprep → merge → cohmask → unwrap,每个阶段一个管理器,依次链式提交。
stackSentinel.py 会生成 run_NN_* 文件,每行一条 shell 命令;每行成为一个子作业。命令条数相同的连续步骤会合并为一个组管理器。
监控
insarhub processor -N <name> -w . refresh # 彩色状态表
insarhub processor -N <name> -w . refresh --ls # 每个单元的明细
insarhub processor -N <name> -w . cancel # scancel 管理器与全部子作业
refresh 报告的是整个工作目录的状态,而不是最近一次提交。进度按磁盘上的产品统计,因此一个跨五次续跑才完成的阶段显示为 192/192,而不是最近一次恰好补齐的 19 个。SLURM 的实时状态优先于状态标记文件:标记只在阶段结束时写入一次,无法表达"进行中";而一次取消留下的标记,在你重新提交的那一刻就已经过期了。
--ls 列出的是真实标识 — 例如 20221202_t056_118971_iw2,或干涉对的两个日期 — 因此缺失的单元可以直接定位处理。
--config 与动作名
--config 的取值是可选的,因此不要把动作名紧跟其后:--config cancel 会把 cancel 当成配置文件路径。请写成 -w . cancel,或者像 --config --hpc-mode submit 这样让一个开关跟在后面。
试运行(dry run)
--dry-run 只打印计划,不做任何改动 — 不提交作业、不写脚本、不改配置:
[ISCE3_Burst] HPC plan (8 concurrent job(s) max):
cslc 192 job(s)
[ISCE3_Burst] dry run -- nothing submitted, nothing written.
这个保证很关键。生成的管理器脚本里记录了它接下来要链接到哪个阶段,所以一次只覆盖部分阶段的试运行如果重写了这些脚本,就会悄悄切断正在运行的流水线。同样,会写入配置的试运行,会把你只是想试一试的参数永久留给之后的每一次运行。
续跑
已经 SUCCEEDED 的阶段会被跳过。支持部分续跑的阶段 — cslc,以及其他单元开销较大的阶段 — 会直接从命令清单中过滤掉已完成的工作,因此重新提交只会排入缺失的部分:
每次提交某个阶段时,完成标记都会被清空,因为走到这一步就意味着该阶段确实要运行。这些标记按位置命名(cmd_0007.done),而命令清单每次都会重新推导,所以跨清单保留的标记会指向完全不同的工作 — 这正是一个续跑的阶段可能跳过真实作业却仍然报告成功的原因。