OpenLava 完全入门(四):高级功能全解(并行 / 依赖 / 数组 / FairShare)

OpenLava 完全入门(四):高级功能全解(并行 / 依赖 / 数组 / FairShare)

摘要:掌握基础命令后,OpenLava 的高级功能才是提高生产力的关键。本文详细讲解四大高级主题:MPI 并行作业配置、作业依赖(dependency)实现多步骤工作流、作业数组(Job Array)批量提交、FairShare 公平分享深入配置。每个主题都有完整的生产示例,读完你能轻松构建复杂的自动化计算流水线。

前面三篇我们讲了安装部署、基本作业管理、队列与资源。这一篇讲四个高级功能:并行作业、作业依赖、作业数组、FairShare


零、读前必看

0.1 适合谁

  • ✅ 已经能用 bsub 提交普通作业
  • ✅ 需要跑 MPI 并行仿真 / EDA 任务
  • ✅ 想做 DAG 工作流(A 跑完跑 B,B 跑完跑 C)
  • ✅ 一次提交几百个参数化任务
  • ✅ 想深入理解 FairShare 调度

一、并行作业(MPI)

1.1 并行作业类型

OpenLava 支持两种并行模式:

模式 场景 例子
SMP(共享内存) 单节点多线程,OpenMP / pthread 1 台机器 16 核
MPI(跨节点) 多节点分布式并行,MPI 程序 10 台机器 160 核

1.2 SMP 并行

最简单,指定 -n 核数 + span[hosts=1] 保证在同一台机器:

# 申请 8 核,都在同一台机器
bsub -n 8 -R "span[hosts=1]" -o smp.out ./omp_program

1.3 MPI 并行(跨节点)

1.3.1 准备工作

MPI 并行需要:

  1. MPI 库(OpenMPI / MPICH / Intel MPI)
  2. 节点之间 SSH 免密登录
  3. NFS 共享可执行文件和数据
# 安装 OpenMPI
yum install -y openmpi openmpi-devel
# 或 Ubuntu: apt install -y openmpi-bin libopenmpi-dev

# 配置 SSH 免密(lsf 用户)
su - lsf
ssh-keygen -t rsa -N "" -f ~/.ssh/id_rsa
cat ~/.ssh/id_rsa.pub >> ~/.ssh/authorized_keys
chmod 600 ~/.ssh/authorized_keys

# 测试免密(要能连所有计算节点)
ssh compute01 hostname

1.3.2 提交 MPI 作业

# 申请 32 核,分布在 4 台机器(每台 8 核)
bsub -n 32 -R "span[ptile=8]" \
  -o mpi.out -e mpi.err \
  "mpirun -np 32 /data/mpi_program"

# 或者用 bsub 内置的启动方式(LSF 兼容写法)
bsub -n 32 -R "span[hosts=4]" mpirun.lsf /data/mpi_program

1.3.3 span 参数详解

span 写法 含义
span[hosts=1] 只在 1 台机器上(SMP)
span[hosts=N] 分布在 N 台机器上
span[ptile=N] 每台机器最多 N 个进程
默认(不写) 调度器自由分配
# 16 核,每台机器 4 核 → 用 4 台机器
bsub -n 16 -R "span[ptile=4]" mpirun ...

# 32 核,用 8 台机器
bsub -n 32 -R "span[hosts=8]" mpirun ...

1.3.4 绑定 CPU 亲和性

# 绑核(每个进程绑到一个物理核)
bsub -n 16 -R "span[hosts=2] affinity[core(1)]" mpirun ...
**生产建议**:CPU 密集型 MPI 作业一定要绑核,否则进程在核之间飘,性能下降 10-30%。

1.3.5 查看分配的节点

# 作业脚本里可以看
echo $LSB_HOSTS        # 所有分配的主机名
echo $LSB_MCPU_HOSTS   # 格式:host1 N host2 M
echo $LSB_DJOB_NUMPROC # 总进程数

# 例:$LSB_MCPU_HOSTS = "compute01 8 compute02 8"

在脚本里用 LSB_MCPU_HOSTS 生成 hostfile:

#!/bin/bash
#BSUB -n 16
#BSUB -R "span[hosts=2]"

# 生成 hostfile
echo $LSB_MCPU_HOSTS | xargs -n2 | while read host n; do
    for ((i=0; i<n; i++)); do echo $host; done
done > hostfile.$LSB_JOBID

mpirun -hostfile hostfile.$LSB_JOBID -np $LSB_DJOB_NUMPROC ./my_mpi_prog

rm -f hostfile.$LSB_JOBID

1.4 并行作业常见问题

问题 原因 解决
跨节点跑不起来 SSH 免密没配好 手动 ssh 测试
程序找不到 节点上没有可执行文件 放 NFS 共享目录
性能差 没绑核 / 跨机架 affinity[core(1)]
节点不够 槽位不足 bjobs -p 看原因

二、作业依赖(Dependency)

2.1 依赖是什么

作业 B 要等作业 A 完成后才能开始,这就是依赖。可以构建 DAG(有向无环图)工作流。
    compile (job 1001)
        │
        ▼
    test_pass (job 1002)
        │
   ┌────┴────┐
   ▼         ▼
 sim_a     sim_b    (并行跑)
(1003)    (1004)
   │         │
   └────┬────┘
        ▼
   report_gen (1005)

2.2 基本依赖语法

# 作业 2 等作业 1 完成(DONE 状态)
bsub -w "done(1001)" job2.sh

# 作业 3 等作业 1 和作业 2 都完成
bsub -w "done(1001) && done(1002)" job3.sh

# 作业 3 等作业 1 或作业 2 任意一个完成
bsub -w "done(1001) || done(1002)" job3.sh

2.3 依赖条件

条件 含义
done(jobid) 作业正常结束(退出码 0)
exit(jobid) 作业异常结束(非 0 退出码)
ended(jobid) 作业结束(不管成功失败)
running(jobid) 作业开始运行了
suspended(jobid) 作业被挂起了
post_done(jobid) 作业 post-exec 完成

2.4 组合条件

# A 完成且 B 完成
-w "done(A) && done(B)"

# A 完成或 B 完成
-w "done(A) || done(B)"

# A 完成且 (B 或 C 完成)
-w "done(A) && (done(B) || done(C))"

# 取反:A 没完成
-w "!done(A)"

2.5 完整流水线示例

#!/bin/bash
# 提交一条完整的编译-测试-仿真-报告流水线

# Step 1: 编译
COMPILE_JOB=$(bsub -J compile -o compile.log "cd /src && make -j8" | awk -F'[<>]' '{print $2}')
echo "编译作业: $COMPILE_JOB"

# Step 2: 测试(等编译完成)
TEST_JOB=$(bsub -J test -w "done($COMPILE_JOB)" -o test.log "./run_tests.sh" | awk -F'[<>]' '{print $2}')
echo "测试作业: $TEST_JOB"

# Step 3: 仿真 A 和 B(等测试通过,并行跑)
SIM_A=$(bsub -J sim_a -w "done($TEST_JOB)" -n 8 -o sim_a.log "./run_sim.sh A" | awk -F'[<>]' '{print $2}')
SIM_B=$(bsub -J sim_b -w "done($TEST_JOB)" -n 8 -o sim_b.log "./run_sim.sh B" | awk -F'[<>]' '{print $2}')
echo "仿真 A: $SIM_A, 仿真 B: $SIM_B"

# Step 4: 生成报告(等两个仿真都完成)
REPORT=$(bsub -J report -w "done($SIM_A) && done($SIM_B)" -o report.log "./gen_report.sh" | awk -F'[<>]' '{print $2}')
echo "报告作业: $REPORT"

2.6 依赖 + 作业名

依赖可以用作业名(job name)代替 job id,更灵活:

# 等名叫 compile 的作业完成
bsub -w "done(compile)" job2.sh

# 等用户 lsf 的名叫 compile 的作业完成
bsub -w "done(compile, lsf)" job2.sh

# 通配符
bsub -w "done(sim_*)" collect.sh
**注意**:作业名可以重复,用名字匹配会匹配所有同名作业。生产里建议用 job id 更可靠。

2.7 查看依赖

# 看作业的依赖条件
bjobs -l 1005 | grep -i depend
# 依赖关系: done(1003) && done(1004)

2.8 常见场景

场景 依赖写法
顺序执行 -w "done(prev_job)"
fan-out(一对多) 多个作业都 -w "done(prev)"
fan-in(多对一) -w "done(a) && done(b) && done(c)"
失败重试 检查 exit,重新提交
条件分支 done(A) 跑 B,exit(A) 跑 C

三、作业数组(Job Array)

3.1 作业数组是什么

一次提交 N 个几乎一样的作业,只有一个参数(索引号)不同。比写 for 循环提交高效得多。

典型场景

  • 参数扫描(遍历 100 组参数跑仿真)
  • 批量处理 1000 个文件
  • Monte Carlo 模拟(跑 500 个随机种子)

3.2 基本用法

# 提交 10 个作业,索引 1-10
bsub -J "myarray[1-10]" -o "out_%I.log" "echo Job index: \$LSB_JOBINDEX; sleep 10"

# 查看
bjobs
# JOBID   USER    STAT  QUEUE    JOB_NAME        NEXEC_HOST
# 2001    lsf     RUN   normal   myarray[1]        1
# 2001    lsf     RUN   normal   myarray[2]        1
# ...
作业数组共用一个 job id,用 `[index]` 区分子作业。

3.3 索引格式

# 连续数字
-J "array[1-100]"

# 步长为 5
-J "array[1-100:5]"    # 1, 6, 11, ..., 96

# 不连续
-J "array[1,3,5,7,9]"

# 混合
-J "array[1-5,10,20-25]"

3.4 在脚本里用索引号

环境变量 $LSB_JOBINDEX 就是当前子作业的索引:

#!/bin/bash
#BSUB -J "sim_array[1-100]"
#BSUB -o "logs/sim_%I.out"
#BSUB -e "logs/sim_%I.err"
#BSUB -n 4
#BSUB -R "rusage[mem=4096]"

echo "Running simulation $LSB_JOBINDEX"

# 用索引构造参数
INPUT_FILE="input_${LSB_JOBINDEX}.dat"
OUTPUT_FILE="output_${LSB_JOBINDEX}.dat"

./run_simulation.sh $INPUT_FILE $OUTPUT_FILE

echo "Simulation $LSB_JOBINDEX done"

提交:

bsub < sim_array.sh

3.5 限制同时运行的数量

# 总共 100 个,但最多同时跑 20 个(防止打爆集群)
bsub -J "array[1-100]%20" command
这个 `%N` 语法叫 **job array throttle**,生产环境非常有用——一下提交 1000 个会把集群资源全占满。

3.6 作业数组管理

# 查看所有子作业
bjobs 2001
bjobs -J "myarray[*]"

# 杀掉整个数组
bkill 2001

# 杀掉单个子作业
bkill "2001[5]"

# 杀掉第 10-20 个
bkill "2001[10-20]"

# 挂起
bstop "2001[1-10]"

# 看历史
bhist 2001

3.7 作业数组 + 依赖

# 等所有子作业都完成
bsub -w "done(2001)" collect_results.sh

# 等第 5 个子作业完成
bsub -w "done(2001[5])" special_task.sh

3.8 完整生产示例:参数扫描

#!/bin/bash
# 提交 500 个参数扫描作业 + 汇总作业

# 1. 确保日志目录存在
mkdir -p logs results

# 2. 提交作业数组(500 个,最多同时 50 个)
ARRAY_JOB=$(bsub -J "param_scan[1-500]%50" \
  -n 2 -R "rusage[mem=2048]" \
  -o "logs/scan_%I.out" -e "logs/scan_%I.err" \
  'python /scripts/scan.py --param $LSB_JOBINDEX --output results/result_$LSB_JOBINDEX.csv' \
  | awk -F'[<>]' '{print $2}')

echo "参数扫描作业数组: $ARRAY_JOB"

# 3. 汇总作业(等所有子作业完成)
COLLECT_JOB=$(bsub -J collect_results \
  -w "done($ARRAY_JOB)" \
  -o "logs/collect.log" \
  'python /scripts/collect_results.py --input results/result_*.csv --output final_report.csv' \
  | awk -F'[<>]' '{print $2}')

echo "汇总作业: $COLLECT_JOB"

四、FairShare 公平分享

4.1 FairShare 是什么

**FairShare 是一种调度策略,保证用户/项目之间公平分配资源。用得多的用户优先级降低,用得少的用户优先级升高。**

没有 FairShare:用户 A 一次提交 1000 个作业,占满所有资源,其他人只能干等。

有了 FairShare:每个用户有份额,A 用多了优先级下降,B 的作业就能插进来。

4.2 配置 FairShare

lsb.queues 里配置:

Begin   Queue
QUEUE_NAME   = normal
PRIORITY     = 30
FAIRSHARE    = USER_SHARES[[user1,10] [user2,5] [default,1]]
End     Queue

份额含义

  • user1 有 10 份,user2 有 5 份,其他人默认 1 份
  • 份额越多,能分到的资源越多
  • 长期看,资源分配比例趋近于份额比例

4.3 FairShare 调度原理

调度优先级 = 基础优先级 + FairShare 调整值

FairShare 调整值基于历史使用量份额的比值:

  • 使用量 < 份额 → 优先级上升 → 更容易调到
  • 使用量 > 份额 → 优先级下降 → 更难调到

4.4 查看 FairShare 使用情况

# 看队列的 FairShare 情况
bqueues -l normal

# 看用户的 FairShare 使用
busers -q normal
# 或
bfairshare -q normal

4.5 项目级 FairShare

Begin   Queue
QUEUE_NAME   = normal
PRIORITY     = 30
FAIRSHARE    = PROJECT_SHARES[[chipA,30] [chipB,20] [chipC,10] [default,5]]
End     Queue

提交作业时指定项目:

bsub -P chipA ./job.sh

4.6 多层 FairShare(用户组内再分)

FAIRSHARE = USER_GROUP_SHARES[[group1, 50, [user1,3] [user2,2]] [group2, 50, [default,1]]]

意思:

  • 两个组各占 50% 资源
  • group1 内部 user1:user2 = 3:2
  • group2 内部大家均分

4.7 FairShare 常见参数

参数 含义 推荐值
USER_SHARES 用户份额 按重要性分配
PROJECT_SHARES 项目份额 按项目权重
FAIRSHARE_ADJUSTMENT_INTERVAL 调整间隔(秒) 120
HIST_HALF_LIFE 历史半衰期(秒) 3600(1 小时)
**HIST_HALF_LIFE**:历史使用量的衰减速度。值越大,历史越"重要"——用户用多了要等更久才能恢复优先级。值越小,越容易恢复。

五、其他高级功能

5.1 作业钩子(esub / eexec / eepilog)

钩子 时机 用途
esub 作业提交前 检查/修改提交参数
epsub 作业提交后 记录审计日志
eprolog 作业开始前 准备环境、挂载目录
epilog 作业结束后 清理资源、回收 license

示例:eprolog 检查输入文件是否存在

#!/bin/bash
# /opt/openlava/etc/eprolog.sh
if [ ! -f "$LSB_JOBNAME_INPUT" ]; then
    echo "Input file not found!" >&2
    exit 1
fi
exit 0

配置:

# lsb.params
BEGIN_PARAGON = /opt/openlava/etc/eprolog.sh
END_PARAGON   = /opt/openlava/etc/epilog.sh

5.2 运行时限制

# 内存限制(超过自动杀)
bsub -M 8192 command     # 单位 KB,8192KB = 8MB??不对
# 不同版本单位可能不同,注意查文档

# CPU 时间限制
bsub -c 2:00 command     # 2 小时 CPU 时间

# Wall clock 时间限制
bsub -W 2:00 command     # 2 小时墙钟时间

5.3 自动重跑

# 作业失败自动重跑,最多 3 次
bsub -r "exit(0)" -N 3 command
# 注意:不是所有版本都支持 -N 重跑次数,看具体版本

生产里更常用 shell 脚本里自己写重试逻辑。

5.4 作业分组

# 提交到一个作业组
bsub -g /dev/teamA command

# 查看作业组
bjobs -g /dev/teamA

# 作业组级别的 FairShare
# 配置在 lsb.users 里

5.5 资源预留(Reservation)

# 预约明天下午 3 点的 10 个核,跑 2 小时
bsub -R "reserve" -n 10 -b "2024:02:01:15:00" -W 2:00 command
资源预留用于:
- 重要会议前保证有资源跑演示
- 紧急任务确保能按时开始
- 大规模并行作业一次性占足够资源

六、高级功能速查表

功能 命令/参数
SMP 并行 bsub -n 8 -R "span[hosts=1]"
MPI 并行 bsub -n 32 -R "span[ptile=8]"
作业依赖 -w "done(jobid)"
作业数组 -J "array[1-100]%20"
数组索引 $LSB_JOBINDEX
FairShare FAIRSHARE = USER_SHARES[...]
查看 FairShare busers / bfairshare
内存限制 -M 8192
运行时限 -W 2:00
CPU 绑核 -R "affinity[core(1)]"
资源预留 -b "2024:02:01:15:00"

七、写在最后

这四个高级功能是 OpenLava 从"能用"到"好用"的关键:

  • 并行作业:让你用得动大任务(MPI/SMP)
  • 作业依赖:让你搭得起工作流(DAG)
  • 作业数组:让你批量任务一键搞定
  • FairShare:让你集群用得公平(大家都有得用)

下一篇(最后一篇)我们讲生产部署与运维:集群高可用、监控告警、日志管理、性能调优、常见故障排查、扩容缩容。


你最想用哪个高级功能?有没有被某个功能坑过? 评论区聊聊。

OpenLava #LSF #MPI #作业依赖 #作业数组 #FairShare #HPC #运维

发表回复

后才能评论