1. 先搞清楚 LSF 命令体系在管哪三件事
刚接手一套 LSF 集群的人,最容易犯的错不是命令记不住,而是把 LSF 当成"单机版的 nohup 加个队列"。看着都是提交一个 shell 脚本然后等结果,实际上区别大得很:单机上你的瓶颈是你那台机器的 CPU 和内存,LSF 上你的瓶颈是队列策略、资源声明、槽位额度这三样东西的组合。同样一份脚本,别人提交三分钟跑起来,你提交三小时还在 PEND,问题基本不在脚本本身。
lsf这一套命令(LSF 是 Load Sharing Facility 的缩写,IBM Spectrum LSF 是它延续下来的版本)本质上做三件事:把作业交出去、把作业的状态看清楚、在作业跑偏的时候干预它。对应下来就是三组命令:
- 提交组:
bsub是绝对主力,bapp、bswitch是配合它的辅助。 - 观测组:
bjobs、bpeek、bhist、bacct、bqueues、bhosts、lsload、lsid、btop。 - 干预组:
bkill、bmod、brequeue、bsuspend/bresume。
新手常见的路径是死记bsub的参数,结果一出问题就抓瞎——因为bsub只负责"投递",投出去之后发生了什么,全靠在观测组那几条命令上。我见过太多人排错的方式是反复 kill 掉重提,重提五次还在 PEND,然后开始怀疑集群坏了。其实用bjobs -l看一眼 PENDING REASON 字段,答案就写在那里。
这篇文章按"投递—观测—干预—排错"的顺序把常用命令过一遍,重点放在每条命令为什么这么用、以及输出里哪些字段才是真正要看的。文中给的参数值和输出字段名基于 LSF 10.x 系列的常见行为,不同集群的配置文件(lsf.conf、lsb.queues、lsb.params)会带来差异,遇到对不上的字段,以你们集群bparams、bqueues -l的实际输出为准。
适合谁看:刚上集群要跑第一批作业的学生和工程师;从 SGE/Slurm 迁过来、命令得重新认的人;以及带了小团队、需要给组员写一份"集群使用约定"的人。如果你只想要一张命令速查表,网上到处都是;这里想给的是判断依据。
2. bsub 提交作业:参数不是越多越好,而是要对上资源画像
2.1 最常被写错的四个参数
bsub的参数有几十个,日常真正常用的也就十来个。但就是这么十来个,坑最集中。先看一张对比表,把容易混淆的几组概念拆开:
| 参数 | 字面理解 | 实际作用 | 典型误用 |
|---|---|---|---|
-n | 核数 | 申请的作业槽位数(slot),是否等于物理核取决于集群配置 | 写成超过单机核数又不加 span,永远排不上 |
-R "rusage[mem=4096]" | 内存 | 向调度器预留4096MB 内存 | 和-M搞混,以为写一个就够了 |
-M 4096 | 内存上限 | 单进程内存超过就触发 TERM_MEMLIMIT | 只写-M不写rusage,调度器不知道你要占多少 |
-W 02:00 | 时限 | 作业最长运行时间,超时被终止 | 单位搞错,-W 120是 120 分钟不是 120 秒 |
-n这个参数值得多说一句。在很多集群里,-n 8意味着"给我 8 个槽位",而一台 32 核的机器按配置可能只切出 16 或 32 个槽位。如果你写-n 64而集群单机最多 32 槽,又没写span,调度器会尝试跨机器分配,这时候你的程序如果不是 MPI 或者分布式框架,跨机器只会让性能更差,甚至因为共享文件系统路径没对齐而直接失败。所以单机多线程的程序,一定要加span[hosts=1],把分配限制在一台机器上。
-W的格式有三种写法:-W 30(30 分钟)、-W 2:00(2 小时)、-W 1:30:00(1 小时 30 分)。这个参数不是建议,是硬约束。队列本身还有一个MAX_JOB_TIME之类的上限,你写的-W超过队列上限,提交阶段就会被拒;写在队列允许范围内但作业实际跑不完,到点就被杀,状态变成TERM_RUNLIMIT。所以正确做法是先估一下这个任务最长能跑多久,再往上留 20%~30% 的余量,而不是随手写个-W 24:00想着一劳永逸——有些集群对超长时限的作业有单独的队列或者需要审批。
2.2 rusage 和 span:为什么你的作业一直 PEND
-R后面接的是一个资源需求字符串,这是 LSF 里最容易写错、也最值得花时间理解的一处。它由若干子句拼成,空格分隔,常见的有:
bsub -n 16 -R "span[hosts=1] rusage[mem=4096]" ./run.sh拆开看:
span[hosts=1]:所有槽位必须在同一台主机上。span[ptile=16]:每台主机最多 16 个槽位,这是另一种常见写法,适合跨节点但控制每节点并发数的场景。rusage[mem=4096]:预留 4096MB 内存,单位是 MB。注意这是每个槽位的预留量还是总量,取决于集群配置中LSB_RUSAGE_MEM之类的设定,很多集群是按 slot 算的,写之前问清楚。rusage[tmp=10240]:预留 10GB 本地临时空间。select[...]:硬性筛选条件,比如select[mem>8000]表示只去内存大于 8GB 的机器。
这里就是"一直 PEND"的头号原因:rusage写得太高,集群里根本没有能满足的主机。比如你的集群最大内存 128GB,每台机器上已经跑了别人的作业,空闲内存只剩 40GB,你写rusage[mem=20000]再乘 16 个槽位,那就是 320GB,调度器算下来永远满足不了,你的作业就一直挂在Job's requirements not satisfied。
排查动作很简单,bjobs -l <jobid>看 PENDING REASON,再用bhosts -R看各机器的资源视图。如果确实是资源写得过高,用bmod改掉rusage就能重新参与调度,不用 kill 重提。这一点在后面的干预章节还会详细说。
提示:
select和rusage的区别要分清——select是"我必须在什么样的机器上跑",满足不了就永远不跑;rusage是"我预计要占多少资源",调度器按这个做预留和计费,写大了会降低被调度的机会,写小了可能在运行时被 OOM 或者超限终止。宁可略高一点,也别离谱地高。
2.3 用 #BSUB 注释写模板,比命令行更可维护
bsub支持把参数写在脚本里,用#BSUB开头的注释行声明。长命令一旦超过三四个参数,我就强烈建议改成脚本内声明,理由是:命令行参数散落在 shell 历史里,下次想复用要翻半天;写在脚本里,脚本本身就是完整的作业描述,可以进版本库、可以给别人直接跑。
#!/bin/bash #BSUB -J align_demo #BSUB -q normal #BSUB -n 16 #BSUB -R "span[hosts=1] rusage[mem=4096]" #BSUB -W 04:00 #BSUB -o logs/%J.out #BSUB -e logs/%J.err cd /work/project/demo || exit 1 ./bin/run_align --threads 16 --input sample.fq提交的时候直接bsub < run.sh就行。这里有几个细节值得单独拎出来:
第一,-o和-e指定的目录必须事先存在,LSF 不会帮你创建父目录,目录不存在的话提交可能成功但输出丢失,或者直接报错。我习惯在脚本开头写mkdir -p logs或者提交前手动建好。
第二,%J是作业 ID 占位符,还有%I表示数组作业的索引、%J在外层目录里也能用。用%J命名输出文件的好处是同一个脚本反复提交不会互相覆盖,回溯时也能凭文件名直接对应到bjobs里的作业号。
第三,cd那一行不能省。LSF 执行脚本时的工作目录不一定是脚本所在目录,特别是你用bsub < xxx.sh这种方式提交时,工作目录往往是你当前所在的目录。所以脚本里凡是相对路径,都要先cd到自己期望的位置,并且|| exit 1让路径错误立刻暴露。
2.4 依赖、数组、交互式:三类特殊提交场景
除了常规提交,还有三种场景几乎每个人都会遇到。
作业依赖。用-w指定前置条件:
bsub -w "done(101)" -J step2 ./step2.sh bsub -w "ended(102)" -J step3 ./step3.shdone()要求前置作业正常完成(退出码 0),ended()只要作业结束就行,不管成功失败。链路长的时候可以串成流水线,比手动盯着要省事得多。但要注意:依赖作业处于 WAIT 状态时也占一个作业槽位,如果你用脚本一次性提交几百个带依赖的作业,可能撞上用户级别的作业数上限(MAX_JOBS之类的参数),表现为提交时报Job not submitted: too many jobs之类。遇到这种情况要么分批提交,要么让上游脚本用-K同步等待的方式串行推进。
数组作业。同一个脚本要跑几十个不同参数,用-J "name[1-50]"提交一个数组作业,脚本里用$LSB_JOBINDEX取当前索引:
#BSUB -J sweep[1-50] #BSUB -o logs/sweep_%I.out ... PARAM=$(sed -n "${LSB_JOBINDEX}p" params.txt) ./run.sh "$PARAM"数组作业的好处是统一管理,bkill "sweep[1-50]"一条命令全停;坏处是索引全展开成独立作业,同样受用户作业数上限约束。
交互式作业。bsub -I -n 4 -q interactive ./bash会开一个在计算节点上的终端,用来调试环境和依赖、验证库路径非常方便。但交互式作业通常有独立的短时限队列,别拿它跑长任务,占着终端槽位不放既影响别人也可能被管理员清理。调试完就退出,把正式任务用批处理方式提交。
3. 作业状态怎么看:bjobs 的输出列和 PENDING 原因解读
3.1 bjobs 的常用组合与自定义输出
bjobs不带参数只看自己的、近期还在系统中的作业。真要用起来,得记住这几个组合:
| 命令 | 含义 | 使用场景 |
|---|---|---|
bjobs | 自己当前的活跃作业 | 日常快速扫一眼 |
bjobs -a | 包含最近结束的作业 | 刚跑完想确认结果 |
bjobs -p | 只看 PEND | 排查为什么排不上 |
bjobs -r | 只看 RUN | 确认哪些在跑 |
bjobs -w | 宽格式输出 | 列多了不换行 |
bjobs -l <id> | 单个作业的详细信息 | 看 PENDING REASON、资源需求 |
bjobs -sum | 按用户/队列汇总 | 概览集群负载 |
默认输出列是JOBID USER STAT QUEUE FROM_HOST EXEC_HOST JOB_NAME SUBMIT_TIME。这些列里,EXEC_HOST是我最常看的——它直接告诉你作业落在哪台机器上,后面要ssh上去查环境、看nvidia-smi、确认本地磁盘用量,全靠这一列。注意EXEC_HOST显示格式通常是主机名:槽位数,比如node07:16。
列的顺序和内容可以自己定制,用-o指定字段名,配合-w:
bjobs -o "jobid stat queue slots exec_host job_name" -w这个用法在写监控脚本时特别有用。把bjobs的输出按空格切成字段,喂给 awk 做统计,就能定期检查"我的作业有多少在 PEND、平均排队多久"。字段名在不同 LSF 版本里略有差异,可以先跑一次bjobs -o "jobid stat"确认字段名被接受,再逐步加。
3.2 PENDING 原因的完整分类与对应动作
bjobs -l输出里最值钱的就是PENDING REASON这一行。它不是提示,是诊断结论。常见值和对策:
| PENDING REASON | 本质原因 | 你要做的事 |
|---|---|---|
Waiting for scheduling decision | 正常排队,调度器还没轮到 | 等,或者换优先级更高的队列 |
Job's requirements not satisfied | -R里的资源全集群都满足不了 | 降低rusage/-n,检查span写法 |
Waiting for a host | 有资源但被别的作业占着 | 看bhosts的NJOBS/MAX,等待或降需求 |
Not enough job slots | 撞到用户或队列的槽位上限 | 看bqueues -l里的JL/U、MAX |
Queue is not open | 队列被管理员关闭 | 换队列,或联系管理员确认开放时间 |
Job dependency | -w指定的前置作业没结束 | 用bjobs查前置作业状态 |
Job group has reached the limit | 作业组配额用满 | 等组内旧作业释放 |
Waiting for a pending job | 受队列内的排序策略影响 | 一般会随调度推进自动消失 |
这张表里最值得琢磨的是Not enough job slots。它跟你申请的资源没半点关系,纯粹是配额问题。比如队列里JL/U(每个用户在该队列的最大槽位)是 128,你一个人已经占了 128 个槽位,再提交就全 PEND,哪怕集群空着一半机器。这种时候要么等自己的作业跑完,要么去跟管理员申请提高配额——反复重提是没用的。
还有一类情况比较隐蔽:Queue is not open。有些集群的队列只在特定时段开放,或者被管理员临时 close 做维护。bqueues输出的 STATUS 列会显示Open、Closed、Inact、Open:Inact等,看一眼就知道。Inact表示队列暂时不调度新作业,通常是内部原因,等就行。
3.3 退出状态码:DONE 之外的每一种都有含义
作业跑完之后,bjobs -a或bhist里看到的 STAT 字段决定了你下一步该干什么:
DONE:正常结束,退出码 0。可以放心取结果。EXIT:进程退出码非 0。去看 stderr 文件。TERM_RUNLIMIT:超过-W时限被杀。要么优化程序,要么调大时限(如果队列允许)。TERM_MEMLIMIT:内存超限被杀。这个是-M或者rusage[mem]触发的。TERM_OWNER:被自己bkill掉的。看到这个先想想是不是自己误杀了。TERM_ARM:有些集群配了自动重排队策略,作业被杀后回到 PEND,准备重跑。ZOMBIE:通常是收到了SIGUSR2之类的信号,进程已经不能正常退出,卡在某个状态。需要人工bkill -s SIGKILL清掉。PSUSP/USUSP/SSUSP:分别是提交即挂起、用户主动挂起、系统因资源紧张挂起。SSUSP 很常见——集群负载高的时候,调度器会把低优先级作业临时挂起给高优先级作业让路,等负载降下来会自动恢复运行,不用管它。
这几行里,TERM_MEMLIMIT和TERM_RUNLIMIT是最容易反复出现的。判断方法很直接:如果同一份作业改了几次参数还是这两种状态,说明你对程序资源画像的估计严重偏低,这时候不要再微调了,用bacct -l看看历史作业实际用了多少内存和 CPU 时间,用实测数据反推该写多少。
4. 队列和主机资源:bqueues、bhosts、lsload 的读法
4.1 bqueues -l 里哪些字段决定你能不能排上
bqueues不加参数只列队列名和粗略状态,真正有用的是-l:
bqueues -l bqueues -l normal输出会包含该队列的调度参数和资源限制。需要重点看的几项:
PRIO:队列优先级。数值越大优先级越高,跨队列比较时才有意义。STATUS:Open、Closed、Inact、Open:Inact。MAX:队列允许的最大槽位总数。JL/U:每个用户在该队列允许的最大槽位。这条最容易撞。JL/P:每个处理器允许的作业数。NJOBS、PEND、RUN:当前队列里的作业数、排队数、运行数。
如果PEND数字远大于RUN,并且RUN已经接近MAX,那就说明整个队列都被占满了,你在排队很正常,只能等。反过来如果RUN很小而你在 PEND,那就是你自己的资源声明或配额出了问题,不是集群整体拥堵。
bqueues -l的输出长度跟集群配置有关,有的集群一个队列的配置能刷屏两屏。我的习惯是只盯上面那几项,其他的SLA、抢占策略、预留给特定组的槽位(RESERVE)之类的信息,只在排不上队的时候才深挖一层。
4.2 bhosts 的 STATUS 与 -R 资源视图
bhosts给出的是机器视角:
bhosts # 概览 bhosts -l node07 # 单机详情 bhosts -R # 显示资源指标概览的列是HOST_NAME STATUS JL/U MAX NJOBS RUN SSUSP USUSP RSV。STATUS 的取值很关键:
ok:正常可调度。closed:不再接收新作业,但已经跑的继续跑。管理员做维护前会这么设。closed_Full:满了,槽位全被占。closed_Excl:被独占作业占住。unavail:短暂不可用,通常是正在被调度或重启。unreach:联系不上,可能是机器宕了或者守护进程没起来。
看到unreach就不要再提交到这台机器了,等管理员处理。看到closed不用慌,你的作业不会被赶走,只是新作业不会往那儿投。
bhosts -R展示的是资源配置,比如每台机器的mem、maxmem、ncores、ncpus之类的指标。这个输出在判断"我的rusage[mem=...]是不是写得离谱"时特别直观:把集群各机器可用的 mem 值扫一遍,取个中位数,你的申请量就不该显著超过它。
4.3 lsload 与 btop:区分"集群满了"和"我写错了"
bhosts看的是调度层面的占用情况,lsload看的是机器实时负载:
lsload -w输出列包括r15s、r1m、r15m(1 秒/1 分钟/15 分钟的负载指数,数值通常表示每槽位的运行队列长度)、ut(用户时间占比)、pg(换页率)、mem、swp、tmp等。判断方法:
r1m和r15m都远大于 1,说明机器过载,作业跑起来会互相抢。swp有持续的非零值,说明在换页,内存压力已经很大了。ut接近或超过 100,CPU 是真忙。
btop是交互式的,类似top,能看到 LSF 视角下各主机的负载和作业分布。它适合在"我的作业怎么这么慢"的时候扫一眼——如果发现作业所在机器上r1m是 30,那慢就不奇怪了,不是你的程序问题,是资源竞争。
这两个命令组合起来能回答一个很实际的问题:你是真的在排队,还是排上了但跑得很慢。前者看bqueues+bjobs,后者看lsload+btop。很多新手把这两种情况混为一谈,一看作业没进展就去改bsub参数,方向完全错了。
5. 干预作业:bpeek、bmod、bkill、brequeue 的正确姿势
5.1 bpeek 看输出,别急着 kill
作业跑起来之后,-o指定的输出文件在作业结束前往往是空的或者不完整的,因为 LSF 会把输出先缓存在本地,作业结束才回传(或者按配置定期刷新)。想看运行中的实时输出,用bpeek:
bpeek <jobid> bpeek -f <jobid> # 类似 tail -fbpeek只是"看一眼",不会中断作业。它的输出可能滞后几分钟,取决于集群的LSB_STDOUT_DIR和刷新策略。有些程序自己写了日志文件,直接去EXEC_HOST上tail -f那个日志文件更实时——前提是你有权限登录计算节点。
我踩过的一个坑:某次作业跑了六个小时没输出,我以为是死循环,直接bkill重提。第二次还是六小时无输出,我才去bpeek,发现程序在正常处理,只是把日志写到了内部文件而不是 stdout。白白浪费了十二小时机时。现在我的习惯是 kill 之前必须bpeek一次,或者 ssh 上去ps看一眼进程状态,确认是卡住还是正常在算。
5.2 bmod 改限制,为什么有时候不生效
bmod用来修改已提交作业的属性,最常用的是改时限和改队列:
bmod -W 06:00 <jobid> # 改时限 bmod -q high <jobid> # 换队列(一般只能对 PEND 的作业) bmod -R "rusage[mem=8192]" <jobid>能改什么、什么时候能改,是有约束的:
- PEND 状态的作业,大多数属性都能改,因为还没分配资源。
- RUN 状态的作业,能改的很少,通常只有时限类的可以改。改
-W对运行中的作业会立即生效,如果你的新时限已经比已运行时间还短,作业会被直接终止——这一点要特别小心,别改成比当前已跑时间还小的值。 - 队列切换基本只对 PEND 生效,运行中的作业换队列一般不支持。
另外一个容易误解的点:bmod改的是作业本身的属性,不会改变你已经写死在脚本里的东西。比如你在脚本里cd到某个目录,bmod改不了它;你在脚本里写死了线程数,改-n也没用。所以bmod是用来救急的,正规做法还是把参数放在#BSUB里管好。
5.3 bkill 的信号选择与收尾清理
bkill默认发SIGTERM,进程有机会做清理。用法:
bkill <jobid> bkill -J myjob # 按作业名杀 bkill -q normal # 杀某个队列里自己的作业 bkill 0 # 杀掉自己所有作业,慎用 bkill -r <jobid> # 杀掉并重新排队 bkill -s SIGUSR2 <jobid> # 触发特定信号,很多程序用它做 checkpointbkill 0这个命令要特别提醒:它会杀掉你当前所有的作业,包括那些跑了很久快出结果的。我见过有人在终端里敲错了数字,bkill 0直接清空了自己的整个作业列表。所以在有多个作业并行的时候,杀之前先bjobs确认一遍 ID。
bkill -r和brequeue的区别值得说一下:bkill -r是杀 + 重排,brequeue <jobid>是直接把运行中的作业放回队列重新调度。两者效果相似,brequeue对某些有 checkpoint 的程序更友好。还有一个brequeue -e,专门用来把EXIT状态的作业重新排队,用在"程序偶发失败想自动重试"的场景。
信号选择也有讲究。默认的SIGTERM是礼貌终止,程序可以捕获它做资源释放。如果程序没响应,bkill -s SIGKILL强杀,但强杀之后作业可能变成ZOMBIE状态,需要额外清理。所以顺序是:先SIGTERM,不生效再考虑SIGKILL。
5.4 bhist 和 bacct:跑完之后怎么复盘
作业结束之后,bjobs默认就不显示了,这时候靠bhist和bacct:
bhist -l <jobid> # 作业完整生命周期:提交、排队、开始、结束的时间点 bacct -l <jobid> # 资源使用统计:CPU 时间、内存峰值、平均负载bhist -l的价值在于它能告诉你排队等了多久和实际跑了多久。如果排队时间远大于运行时间,那优化方向是调整提交策略(比如避开高峰期、改资源声明),而不是优化程序本身。这是个很实用的判断:我有个任务队列等了 8 小时、实际跑了 20 分钟,那就说明资源申请严重不匹配,改小rusage之后基本秒起。
bacct -l给的是资源画像:峰值内存、CPU 时间、平均 CPU 利用率。这些数据用来反向校准你的bsub参数。比如你写rusage[mem=16384],但bacct显示峰值内存只有 3GB,那就是浪费了 13GB 的调度配额,写小一点能显著提高被调度的概率。反过来,如果峰值内存 15.8GB 逼近你写的 16GB,那下次得留足余量,否则早晚撞TERM_MEMLIMIT。
6. 排错实录:几类高频报错的定位链路
6.1 提交直接被拒
症状是敲完bsub立刻返回错误,作业根本没进系统。常见报错和原因:
Queue <xxx> does not exist:队列名拼错。用bqueues列一遍确认。Job not submitted: Too many jobs:撞上用户作业数上限。bjobs -a清理已完成作业的显示,或者等一会儿。-o目录不存在相关报错:先把输出目录建好。Job's resource requirement is invalid:-R字符串语法错。常见的写法错误是rusage[mem=4096, tmp=1024]里用逗号而不是空格分隔多个需求,正确的是rusage[mem=4096] rusage[tmp=1024]或者写成rusage[mem=4096:tmp=1024],具体哪种看集群版本。
这类问题定位最快,因为报错信息明确。要注意的是报错里的队列名和你在bsub里写的名字要逐字比对,包括大小写。
6.2 长时间 PEND 的排查链路
这个最耗时,也最有价值。我的固定排查顺序是:
第一步,bjobs -l <jobid>读PENDING REASON。这一步能解决八成问题,后面几步只是验证。
第二步,如果原因是资源不满足,bhosts -R看集群最大可用资源,bjobs -l里有一行显示你的作业要求什么资源,两者一对比就知道差多少。
第三步,如果原因是配额,bqueues -l <队列>看JL/U、MAX,再bjobs -u all -q <队列> -sum或者busers看当前占用。
第四步,如果PENDING REASON显示正常排队,bqueues里PEND数远大于RUN,那就是集群整体拥堵,没有别的办法。btop确认一下负载,然后接受现实,或者换优先级更高的队列(如果有权限)。
整个链路里,不要做的事是反复 kill 重提。重提不会改变你的资源声明和配额占用,只是让作业在新位置重新排一次队,甚至可能因为提交时间变新而排在后面。
6.3 运行超时和内存超限
TERM_RUNLIMIT和TERM_MEMLIMIT这两个状态的排查方式类似:先用bacct -l看实际用了多少,再决定是改参数还是改程序。
如果是时限问题,先确认队列允许的最大时限,用bqueues -l看,把-W设在合理区间。如果程序本身就跑不完,那说明这个任务不适合放进有时限约束的队列,得考虑拆分(用数组作业分片跑)或者申请特殊队列。
如果是内存问题,要区分是声明不够还是程序泄漏。判断方法:bacct里如果能看出内存随时间单调增长,那大概率是泄漏,改参数只是延后爆炸时间。这时候应该先修程序,再谈提交参数。
注意:改
-M只是放宽了单进程的内存上限,它不会让调度器多给你预留内存。真正影响能不能排上队的是rusage[mem]。两个参数一个管调度、一个管运行时限制,作用完全不同,别只改一个就以为问题解决了。
6.4 作业显示 DONE 但输出是空的
这个坑很典型。作业状态DONE,退出码 0,但-o指定的文件是空的或者找不到。可能的原因:
一是工作目录不对。脚本里的相对路径相对于 LSF 的执行目录,而不是你cd进去的那个,或者你根本没cd。解决办法是在脚本开头打印pwd和ls -l做自检。
二是输出被重定向到别处。程序自己把 stdout 重定向到了内部日志文件,-o抓不到。
三是文件被写到了计算节点的本地磁盘。LSF 的-o会把输出回传到提交节点,但如果程序自己写文件用的是本地路径而不是共享文件系统路径,那些文件就留在计算节点上了,作业结束后可能会被清理。判断方法:bjobs -l里的EXEC_HOST告诉你作业在哪台机器跑过,去那台机器上看本地目录(如果还没被清理)。
这类问题的根本解法是统一路径规范:所有输入输出都用共享文件系统上的绝对路径,本地临时文件显式写到$TMPDIR之类的本地目录并在脚本末尾清理。我见过太多"作业跑完了结果找不到文件"的情况,十有八九是路径问题。
6.5 一些零散但实用的命令
剩下几条命令虽然不常用,但值得记一下:
lsid输出集群名、master 主机和 LSF 版本。多集群环境下,bsub提交到哪个集群由环境变量决定,lsid是确认自己当前在跟哪个集群说话的最快方式。提交行为和预期不一致时,先跑一下lsid。
bparams -a显示调度器的全局参数,比如作业保留时间、输出回传策略。当你不确定"作业结束后多久还能bjobs -a看到"这类问题时,答案在这里。
bapp -l列出集群里的应用模板(application profile)。有些集群把常用软件的参数封装成了 app,bsub -app <name>就能用,比手写一长串参数省事,而且这些模板通常是管理员调优过的。
bswitch <queue> <jobid>把 PEND 中的作业从一个队列挪到另一个。改队列用bmod -q也行,bswitch更像是个专门的快捷方式。
bsuspend <jobid>/bresume <jobid>手动挂起和恢复。挂起后状态是USUSP,不占运行槽位但保留分配,用来给更紧急的任务让路。
最后再分享一个日常习惯。我会给自己维护一个job_status.sh小脚本,里面就三行:
#!/bin/bash bjobs -w echo "---- pending reasons ----" for j in $(bjobs -p -o "jobid" -noheader 2>/dev/null); do bjobs -l "$j" | grep -A1 "PENDING REASON" done跑一次就能同时看到所有作业和每个排队作业的原因,比一条条bjobs -l翻快得多。-noheader这个选项在部分版本里叫-noheader,在另一些版本里是别的写法,先在你们集群上试一下。这类小工具不复杂,但能省下大量重复劳动,尤其在你同时管着几十个作业的时候。