前言:接着上一篇的 RESOURCES 列往下讲
上一篇《lsf.cluster.cluster_name 基础配置详解》讲到,Host 段的 RESOURCES 列可以给主机打静态标签(比如 bigmem、gui),但当时只是简单带过——这些标签到底是怎么“定义”出来的?资源除了“有没有”这种是非题,能不能表示“有多少”、精确到数量?多台主机共享同一批 license,怎么统一管理总量、避免超发?这些问题,答案都在 lsf.shared 的 Resource 段和 lsf.cluster.cluster_name 的 ResourceMap 段里。
这篇是系列第二篇,专门讲透 LSF 资源体系的完整分类、怎么定义自定义资源、怎么把资源精确映射到主机或主机组、以及怎么配置资源的最小/最大使用阈值。读完这篇,你会有能力把 EDA license、GPU 卡、共享存储空间这些 CAD Farm 里最稀缺的资源,管理得像 CPU 核心数一样精确可控。
一、先搞懂 LSF 的资源分类体系
LSF 里“资源”(Resource)这个词含义很广,凡是调度决策会用到的属性都算资源,从 CPU负载、内存这种系统内置的,到 license token 这种管理员自定义的,都在同一套框架下管理。要配置好自定义资源,得先搞清楚三组分类维度。
1.1 按取值类型分类:Boolean / Numeric / String
类型 | 含义 | 典型场景 |
Boolean | 只有“有”或“没有”两种状态,值为 1 或 0 | 标记主机特征:主机是否是 GUI 工作站(gui)、是否装了某软件(linux_verilog) |
Numeric | 数值型,可以参与大小比较、加减运算,且可以设置为“消耗型”(consumable) | license token 数量、GPU 卡数量、共享存储剩余空间 |
String | 字符串型,用于分类标签而非度量 | 机柜编号(rack)、操作系统补丁版本(patchrev)、责任人(owner) |
1.2 按是否随时间变化分类:Static / Dynamic
静态资源(Static)配置好之后不会自己变化,比如主机型号、CPU核数、机柜编号——除非管理员手动改配置文件,否则永远是同一个值。动态资源(Dynamic)则会随时间变化,比如内存使用率、license 当前剩余可用数量,这类资源需要配置 INTERVAL(更新间隔,单位秒),并且往往需要一个外部程序(ELIM,External Load Information Manager)周期性采集真实值并上报给 LIM。
1.3 按作用范围分类:Host-based / Shared
Host-based 资源是“各扫门前雪”,每台主机各自独立统计一份,比如每台机器自己的内存使用率;Shared 资源则是“多台主机共用一个池子”,比如 20 个 VCS license token 由整个仿真农场共享,任何一台主机上的作业申请了 license,池子里的数量就会相应减少,使用其中一台机器上的 shared 资源会影响其他机器看到的可用值。这一点非常关键——CAD Farm 里的 license 管理必须配置成 Shared 资源,而不是让每台主机各自维护一份,否则会出现同一个 license 被多台主机重复计数的问题。
⚠️ 注意:这里有一个很容易踩的理解误区:Static/Dynamic(会不会变化)和 Host-based/Shared(独立统计还是共用池子)是两个完全独立的分类维度,不能划等号——不是“动态资源=Shared 资源”,也不是“静态资源=Host-based 资源”。四种组合在实际场景里都存在,可以对照下表理解:
Host-based(各自独立统计) | Shared(共用一个池子) | |
Static(不随时间变化) | 每台主机的 GPU 数量、CPU 核数——配置好之后固定不变,各台主机各自一份 | 40 个 license token 由 LSF 自己记账管理——总量固定,但由多台主机共用同一份计数 |
Dynamic(随时间变化) | 每台主机实时的 CPU 负载、内存使用率——各台主机的值互不影响 | license server 当前实时剩余的 token 数——由 ELIM 采集,多台主机共用同一个动态数值 |
***消耗型(CONSUMABLE)是数值资源的一个重要属性:作业占用后总量会减少、作业结束后自动归还,绝大多数你想拿来做“配额管理”的自定义资源(license、GPU数量)都应该设置成消耗型;反之像 cpuf(CPU相对速度)这种描述性的数值就不该设为消耗型。
二、lsf.shared Resource 段:定义资源的“元数据”
自定义资源必须先在 lsf.shared 的 Resource 段里“注册”——声明名字、类型、更新间隔等元数据,之后才能在 lsf.cluster.cluster_name 的 Host 段 RESOURCES 列或 ResourceMap 段里被引用,也才能在 bsub -R、lsb.queues 的 RES_REQ 里被使用。
2.1 基本语法
Begin Resource RESOURCENAME TYPE INTERVAL INCREASING CONSUMABLE DESCRIPTION vcs_license Numeric 30 N Y (Synopsys VCS License Token) calibre_dv Numeric 30 N Y (Calibre DRC/LVS License) ngpus Numeric 60 N N (GPU数量) gui Boolean () () () (图形工作站) bigmem Boolean () () () (大内存节点) rack String () () () (机柜编号) owner String () () () (主机责任人) End Resource |
2.2 各字段详解
字段 | 说明 |
RESOURCENAME | 资源名,必填,不能以数字开头,也不能以 inf / nan 开头(避免和第三方库的保留字冲突) |
TYPE | Boolean / Numeric / String 三选一,缺省默认为 Boolean |
INTERVAL | 动态资源的更新间隔(秒),静态资源留空 ();配置了 INTERVAL 就意味着这是一个需要 ELIM 采集的动态资源 |
INCREASING | 描述数值变化与“负载”之间的方向关系:Y 表示数值越大代表负载越高(如已用 license 数),N 表示数值越小代表负载越高(如剩余 license 数,剩余越少负载越高) |
CONSUMABLE | 是否为消耗型资源:Y 表示作业占用后总量减少、结束后归还,license/GPU/存储配额这类资源都应设为 Y |
RELEASE | 可选字段,作业被挂起时是否释放该 Shared Resource 的调度占用(默认为 Y)。注意这里释放的只是 LSF 调度层面对这份资源的记账占用,不等于底层 EDA 工具真的把 license token 还给了 license server——只有在确认作业挂起后底层工具确实会释放 license 时,才适合配置 RELEASE=Y;如果工具挂起后仍然占着 license 不放,配置 RELEASE=Y 反而会导致 LSF 账面可用量和 license server 实际状态不一致,此时应该谨慎评估甚至保持 RELEASE=N |
DESCRIPTION | 资源描述,必填,会在 lsinfo -l 等命令的输出中显示,方便其他管理员理解用途 |
***补充说明两点容易忽略的细节:一是 INCREASING 只影响 LSF 内部如何判断“负载高低”(进而影响负载均衡和调度决策),本身并不直接控制作业能不能申请、能申请多少资源——真正管申请量的是后面要讲的 RESRSV_LIMIT;二是 CONSUMABLE=Y 只对 Numeric 资源有意义,Boolean 资源表达的是“有没有”而不是“有多少”,不存在消耗和归还的概念,所以不要给 Boolean 资源配置 CONSUMABLE=Y。
2.3 静态资源 vs 动态资源的取舍
像 gui、bigmem 这种“主机有没有某个特征”的标签,用静态 Boolean 资源即可,配置一次几乎不用再变。但像 vcs_license 这种“当前还剩多少个可用”的量,理论上应该是动态资源(配合 ELIM 脚本实时查询 license server 的真实剩余数)。不过在实践中,很多 CAD Farm 选择用一种更简单的折中方案:把 license 总量配置成静态消耗型资源(比如固定配置 40 个 token),完全依靠 LSF 自己的调度器记账(谁申请了多少、谁释放了多少)来管理可用余量,而不依赖外部 ELIM 去查询真实的 license server。这种方式配置简单、维护成本低,缺点是如果有人绕开 LSF 直接从命令行启动 EDA 工具占用了 license,LSF 的账本会跟真实情况脱节。
***这里准确说一下 ELIM 的工作机制:ELIM(External Load Information Manager)是 LSF 用来采集集群外部负载/资源信息的外部程序,由管理员编写、放在约定目录下(通常是 LSF_SERVERDIR),可执行文件命名为 elim,如果同一目录下有多个 elim 程序需要分别管理不同资源,也可以按 elim.<资源名> 这样的后缀命名区分。真正负责启动、监控 ELIM 进程的是 LIM 内部的 MELIM(Master ELIM)模块——MELIM 会拉起 ELIM、在它异常退出时重启它、读取它上报的数据并合并成资源报告,再发送给 LIM。对于动态资源,LSF 会根据 ResourceMap 里的 LOCATION 决定在哪些主机上运行对应的 ELIM,并按配置的 INTERVAL 周期性获取最新值。编写 ELIM 脚本的具体细节请参考你所用 LSF 版本的官方文档,这里只需要理解这套“ELIM 采集 → MELIM 汇总 → LIM 分发”的链路。
✅ 最佳实践:如果相关 EDA 工具全部强制通过 LSF 提交,并且 license 使用模型比较稳定,可以采用静态消耗型资源 + LSF 自身调度记账的方案,配置简单、维护成本低;如果存在绕过 LSF 直接申请 license 的入口,或者 license server 上存在多种独立的授权池,则更适合投入精力写 ELIM 脚本去查询 license server 的真实剩余量,做成真正的动态资源,避免账本和现实脱节导致的超发。
三、ResourceMap 段:把资源精确分配到主机
Resource 段只是“定义”了资源的元数据,真正决定“这个资源具体在哪几台主机上、总量是多少”的,是 lsf.cluster.cluster_name 里的 ResourceMap 段。如果一个动态资源没有在 ResourceMap 里配置映射关系,它默认会被当成所有主机共享的资源;如果想要精确控制“这批 license 只归这几台机器的作业使用”,就必须显式配置 ResourceMap。
3.1 基本语法
Begin ResourceMap RESOURCENAME LOCATION vcs_license (40@[all]) calibre_dv (20@[cadsim01 cadsim02] 10@[others]) console (1@[cadmgr01] 1@[cadmgr02]) scratch (500@[cadbig01] 500@[cadbig02] 1000@[cadbig03]) End ResourceMap |
⚠️ 注意:这里有一条容易漏看、但非常关键的官方规则:静态资源必须在 LOCATION 里写初始值(数量@[主机列表]);动态资源则不能写数量,只能写 [主机列表],实际数值由 ELIM 采集上报,写死一个数字反而是错误配置。下面 3.4 节会给出动态资源的正确写法。
3.2 LOCATION 语法详解
写法 | 含义 |
数量@[主机列表] | 静态资源专用写法:指定数量的资源由方括号内的这组主机共享 |
[主机列表](不带数量) | 动态资源专用写法:数量不在这里写死,由 ELIM 周期性采集上报 |
[all] | 关键字,表示集群内所有 server 主机 |
[all ~host1 ~host2] | 用 ~ 排除特定主机,表示除 host1、host2 之外的所有主机 |
[others] | 关键字,表示除本段前面已经显式列出的主机之外,剩余的所有主机 |
[default] | 关键字,表示这份资源实际上不共享、在每台主机上各自独立拥有一份,适用于需要给不同主机配不同数值的非共享静态资源 |
需要说明的是,同一个资源可以在 LOCATION 里被拆成多个方括号分组,每个分组各自是一个独立的共享池;但同一台主机不能同时出现在同一个资源的多个分组里——比如下面的 calibre_dv 拆成两组,host01/host02 共享 15 个,host03/host04 共享另外 5 个,这是两个互不相通的独立资源池,一台主机只能属于其中一个池子,不能既拿这份又拿那份。
3.3 实战示例:EDA license 池的精细化拆分
CAD Farm 里经常会遇到“同一个 license 类型,但公司买了不同规格的授权分给不同项目组”的情况,用 ResourceMap 可以很自然地把这种业务需求映射成配置。这里需要注意:ResourceMap 的 LOCATION 只接受主机名列表(以及 all/others/default 关键字),不能直接把 lsb.hosts 里定义的 HostGroup 名字当成 @[组名] 来引用——下面给出正确写法:
# lsf.shared 中定义资源 Begin Resource RESOURCENAME TYPE INTERVAL INCREASING CONSUMABLE DESCRIPTION calibre_dv Numeric () N Y (Calibre DRC/LVS License) End Resource
# lsf.cluster.fasteda_farm 中按项目组拆分 license 池(直接列出主机名) Begin ResourceMap RESOURCENAME LOCATION calibre_dv (15@[dig01 dig02 dig03 dig04] 5@[ana01 ana02]) End ResourceMap |
如果数字设计组的主机很多、一台台列举太麻烦,可以配合 all 关键字和 ~ 排除写法简化:
Begin ResourceMap RESOURCENAME LOCATION calibre_dv (15@[all ~ana01 ~ana02] 5@[ana01 ana02]) End ResourceMap |
这份配置把 20 个 Calibre license 拆成两个独立的池子:数字设计仿真主机(dig01~dig04)固定能用 15 个,模拟设计仿真主机(ana01、ana02)固定能用 5 个,两边互不侵占——即使数字组的作业把自己的 15 个用完了,也不会占用模拟组的份额,避免了“谁提交得快谁先抢到”的资源争抢问题。
3.4 动态 license 资源:由 ELIM 实时采集
如果希望 license 数量是真正从 license server 实时查询来的(而不是靠 LSF 自己记账),需要在 Resource 段配置 INTERVAL 更新间隔,并配一个自定义的 ELIM 可执行程序,周期性输出真实剩余值;ResourceMap 里同样要做主机映射,但这里最容易犯的错误就是习惯性地照抄静态资源的写法在前面加了数量——动态资源的 LOCATION 不能写数量,只写主机列表:
# lsf.shared:声明为动态资源,60 秒更新一次 Begin Resource RESOURCENAME TYPE INTERVAL INCREASING CONSUMABLE DESCRIPTION calibre_dv_real Numeric 60 N Y (Calibre实时剩余License) End Resource
# lsf.cluster.fasteda_farm:动态资源不写数量,只写主机列表 Begin ResourceMap RESOURCENAME LOCATION calibre_dv_real ([cadmgr01]) End ResourceMap |
⚠️ 注意:对比 3.3 节的静态写法 (15@[dig01 ...]),这里的 calibre_dv_real ([cadmgr01]) 前面没有数量——这不是疏漏,而是动态资源的强制要求:数值由 cadmgr01 上运行的 ELIM 实时查询 license server 得到,配置文件里写死一个数字反而是错误配置,IBM 官方对此有明确说明(“For a static resource, you must define an initial value here as well. Do not define a value for a dynamic resource.”)。
***对于 license server 这类动态 Shared Resource,通常应该把 ResourceMap 映射到负责执行 ELIM 查询的那台主机(比如例子里的 cadmgr01,可以是管理节点,也可以是专门跑 license 查询脚本的主机),由这台主机上的 ELIM 去查询 license server 并上报数值,集群里的其他主机共享这一份数值。这里映射的是“由谁负责采集”,不是“每台主机各自独立运行一份 ELIM、各自维护一份数值”——license 总量本来就是全局唯一的,不需要、也不应该让多台主机重复采集同一个数。如果你的映射写成 [all] 或者更大范围的主机列表,实际含义和行为请以你所用 LSF 版本的官方文档为准,稳妥的做法是像本例这样显式指定一台明确负责采集的主机。
***这里讲的是动态 Shared(多主机共用一份数值)的写法。如果你要采集的动态资源本质是 host-based 的——也就是每台主机各自独立的一份数值、互不共享(比如每台主机自己的某个内部指标),同样不写数量,但对应的写法是 [default](表示每台主机各自拥有一份、由本机的 ELIM 各自上报)或者显式列出参与采集的主机列表 [host1 host2 ...],而不是本节这种指向单一采集节点的写法。两者的关键区别在于:“license 这类全局唯一的量” vs “每台主机各自独立的量”,选错了会导致要么多台主机重复采集同一个全局值、要么本该各自独立的指标被错误地当成了共享值。
四、LSF 里四种“资源限制”机制,千万不要混
这是 LSF 新手最容易踩坑、也是资深管理员最容易讲错的一节。“限制资源使用”在 LSF 里其实对应四种完全不同的机制,作用的阶段、生效的对象都不一样,混在一起讲很容易产生“改了一个参数、结果影响了完全不相关的行为”的困惑。这一节先把四者的边界说清楚,再逐个展开。
机制 | 配置位置 | 回答的问题 | 生效阶段 |
① 主机负载阈值 | lsb.hosts 的 loadSched/loadStop | 这台主机现在的负载状态,还适不适合继续派新作业? | 调度前,持续监控 |
② 资源预留上下限 (RESRSV_LIMIT) | lsb.queues 的 RESRSV_LIMIT | 单个作业提交时,申请的资源量是否落在允许范围内? | 作业提交时,一次性检查 |
③ 资源分配配额 (Resource Allocation Limit) | lsb.resources 的 Limit 段 | 某个用户/队列/项目,同一时刻总共能占用多少资源? | 调度时,持续检查各消费者的当前占用 |
④ 运行时资源用量上限 | lsb.queues 的 MEMLIMIT/CPULIMIT/RUNLIMIT 等 | 作业已经在跑了,实际资源消耗超过上限该怎么办? | 运行中,持续监控并强制执行 |
4.1 机制①:主机负载阈值——只能用于负载类指标,不能用于 Shared 资源
这部分内容第一篇系列文章(lsb 配置详解)里已经讲过语法,这里要特别澄清一个容易混淆、且相当重要的边界:lsb.hosts 里能配置 loadSched/loadStop 两条线的,只能是主机负载类指标(r1m、mem、pg、swp、tmp、ut 等内置负载指标)以及 host-based 的自定义动态负载指标,不能是 Shared 资源。
Begin Host HOST_NAME MXJ r1m mem cadsim01 16 3.5/5.0 2000/500 default ! 3.5/5.0 () End Host |
以 r1m 这一列 3.5/5.0 为例:3.5 是调度阈值(scheduling threshold),表示这台主机的 1 分钟平均负载低于 3.5 时才会被派发新作业;5.0 是挂起阈值(suspending threshold),表示负载超过 5.0 时会把该主机上已经在跑的作业挂起。这套机制只适用于 r1m、mem 这类持续变化的主机负载指标。
⚠️ 注意:千万不要把 calibre_dv 这类 Shared Consumable Resource(比如上一节配置的 license 池)当成 lsb.hosts 的负载阈值来写。IBM 官方明确规定:Shared 资源不能用在 lsb.hosts 的 loadSched/loadStop 阈值列,也不能用在 lsb.queues 的 STOP_COND/RESUME_COND 参数里。license、GPU、共享存储配额这类资源的用量控制,应该通过本节接下来讲的 RESRSV_LIMIT(机制②)和 lsb.resources 的 Limit 段(机制③)来管理,而不是试图在 lsb.hosts 里给它配一条类似 15/2 的阈值线——这是两套完全不同的机制,语法上也不支持这样混用。
4.2 机制②:RESRSV_LIMIT——作业申请的资源范围,超出直接拒绝
RESRSV_LIMIT 解决的问题是“单个作业一次最多/最少允许申请多少资源”,配置在 lsb.queues 里,针对 RES_REQ/rusage 里声明的资源设定一个允许区间。
Begin Queue QUEUE_NAME = drc_lvs DESCRIPTION = DRC/LVS 物理验证队列 RES_REQ = rusage[calibre_dv=1] RESRSV_LIMIT = [calibre_dv=1,4] [mem=2000,64000] End Queue |
⚠️ 注意:这里有一个语法上很容易写错的地方:一个队列定义里 RESRSV_LIMIT 只能出现一次,如果要同时限制多个资源(比如上面的 calibre_dv 和 mem),必须写在同一行、用多个中括号分组表示,而不能像限制多个负载指标那样重复写两行 RESRSV_LIMIT——重复写第二行会覆盖第一行,而不是两条规则同时生效。官方语法是 RESRSV_LIMIT=[res1={min1,}max1] [res2={min2,}max2] ...,多个资源的限制紧跟着写在同一行里。
⚠️ 注意:这里还必须澄清一个常见的错误理解:如果作业申请的资源量超出了 RESRSV_LIMIT 规定的范围(比如申请了 rusage[calibre_dv=8],超过上限 4),IBM 官方的行为是作业直接被拒绝提交(job is rejected),而不是所谓的“自动钳制到边界值”——不存在把 8 自动改写成 4 这种行为。这个区别很重要:如果你的团队看到作业提交失败,应该理解成“这个请求不合法、需要工程师改小申请量重新提交”,而不是“LSF 会帮你自动调整”。
***这个检查发生在作业提交的那一刻,由 mbatchd 立即完成校验——不满足 RESRSV_LIMIT 范围的作业会直接提交失败并返回错误,而不是先被接受、进入 PEND 状态排队等待再被发现不合法。这和后面第③④两种机制不同:③(lsb.resources 的 Limit 配额)是在调度阶段持续检查当前占用是否还有余量,④(运行时资源用量上限)是作业运行过程中持续监控,只有②是在提交的瞬间一次性校验完就有明确结果。
✅ 最佳实践:给 license 类的消耗型资源配置 RESRSV_LIMIT 上限,是防止“一个人的脚本 bug 或者不熟悉规范的新人,一次性申请几十个 license token 导致其他人全部排队”的关键防线,比事后靠人工巡查发现问题要可靠得多。
4.3 机制③:lsb.resources 的 Limit 段——用户/队列/项目的总量配额
RESRSV_LIMIT 管的是“单个作业”,但 CAD Farm 里更常见的需求是“某个用户/项目组,不管开了多少个作业,总共同时最多能占用多少资源”——这正是 lsb.resources 的 Limit 段要解决的问题。它是 LSF 中用于跨作业汇总资源占用、并按用户、队列、主机、项目、应用等消费者维度统一实施资源分配配额的核心机制之一(lsb.queues/lsb.users 里也有 UJOB_LIMIT、MAX_JOBS 等零散的限额参数,但 lsb.resources 的 Limit 段提供的是一套更统一、维度更全的配额框架)。
Begin Limit NAME = CalibrePerUser PER_USER = all RESOURCE = [calibre_dv,4] End Limit
Begin Limit NAME = CalibreProjectCrash PROJECTS = crash RESOURCE = [calibre_dv,10] End Limit |
第一段配置的意思是:不管一个用户同时提交了多少个作业,这个用户名下所有作业加起来占用的 calibre_dv 总量不能超过 4 个(PER_USER = all 表示这条规则对每个用户各自独立生效,而不是所有用户共享一个 4 的额度);第二段则是按 project 维度限制,crash 项目所有作业加起来最多能用 10 个 license。Limit 段支持 USERS/PER_USER、QUEUES/PER_QUEUE、HOSTS/PER_HOST、PROJECTS/PER_PROJECT 等多种消费者维度,也支持 SLOTS、MEM、SWP、TMP、RESOURCE(自定义资源)等多种被限制的资源类型,是 CAD Farm 做精细化配额管理最常用的机制。
⚠️ 注意:实际生产环境中,不建议对同一资源维度同时配置多个功能相近的限制机制(比如同时在 lsb.users 配 MAX_JOBS、又在 lsb.resources 配等价的 Limit)。LSF 不同参数之间的合并与优先级规则并不完全相同,应针对具体参数查阅对应版本的官方文档。为了降低维护复杂度,建议明确规定“这一类限额统一由哪个配置文件负责”,而不是多处重复配置同一件事。
4.4 机制④:运行时资源用量上限——作业真正跑起来后的硬限制
前三种机制都发生在“作业开始运行之前”(调度阶段),而 MEMLIMIT、CPULIMIT、RUNLIMIT、PROCESSLIMIT 这些参数管的是“作业已经在运行了,实际消耗的资源不能超过多少”,是运行期间持续监控、一旦超限就会被 LSF 发信号终止的硬限制。
Begin Queue QUEUE_NAME = sta_bignode MEMLIMIT = 64000 # 默认是单进程内存限制,不是整个作业 CPULIMIT = 48:00 RUNLIMIT = 72:00 End Queue
# lsf.conf:如果要把上面的 64000 理解成整个作业(所有子进程加总)的内存上限, # 必须额外开启这个参数,否则 MEMLIMIT 只对单个进程生效 LSB_JOB_MEMLIMIT=Y |
这里有一个 EDA 工程师非常容易搞混的地方:rusage[mem=10000] 说的是“调度时按 10GB 预留资源”,并不代表进程运行起来真的不能超过 10GB;只有 MEMLIMIT 才是运行时的硬限制,超过了会被 LSF 发送终止信号杀掉。但 MEMLIMIT 默认的语义是单进程内存上限(per-process resident size limit),如果一个作业 fork 出多个子进程,每个子进程各自都按这个上限单独判断,加总起来的整个作业实际可以远超这个数字;只有额外配置了 lsf.conf 里的 LSB_JOB_MEMLIMIT=Y,LSF 才会把同一个作业名下所有进程的内存加总后按整个作业来执行这个限制。
***如果你的集群跑在启用了 cgroup 内存强制(LSB_RESOURCE_ENFORCE 包含 memory)的现代 Linux 环境下,内存限制的实际生效机制会更接近操作系统层面的 cgroup 强制,和这里讲的 LSB_JOB_MEMLIMIT 是两套不完全相同的机制,具体行为请以你的 LSF 版本文档为准,本篇不展开这部分内容。
4.5 一张图分清“申请多少”和“实际用了多少”
把上面四种机制串起来,一个作业从提交到运行结束,会依次经过这样几层检查:
机制 | 回答的问题 | 是否限制实际运行资源消耗 |
rusage[mem=10000](RES_REQ) | 调度时希望预留多少资源? | 否——只是预留意向,不是硬限制 |
RESRSV_LIMIT=[mem=2000,64000] | 允许这个作业申请的 rusage 范围是多少? | 否——只在提交时检查申请量是否合法,超出直接拒绝 |
lsb.resources Limit(如 PER_USER RESOURCE) | 这个用户/队列/项目总共能同时占用多少? | 调度配额——持续检查当前占用总量,达到上限就不再派发新作业 |
MEMLIMIT=64000 + LSB_JOB_MEMLIMIT=Y | 作业运行时实际内存不能超过多少? | 是——运行期间持续监控,超限直接终止作业 |
�� 这四层检查的顺序是:提交作业时先看 RES_REQ 声明了多少(②),再看这个申请量是否落在 RESRSV_LIMIT 允许的范围内(②,超出直接拒绝提交),调度器决定派发前还要看 lsb.resources 的 Limit 配额是否还有余量(③),作业真正跑起来之后才轮到 MEMLIMIT 这类运行时限制介入监控(④)。四层各管一段,缺一不可,也不能互相替代。
4.6 补充:多阶段资源预留(Multi-phase rusage)
有些仿真作业的内存需求会随运行阶段变化——比如启动阶段需要加载大量数据、占用内存较高,进入稳定计算阶段后内存需求回落。rusage 支持配置多阶段用量表达式,让资源预留更贴近作业的真实生命周期,而不是从头到尾都按峰值预留(那样会造成大量浪费)。
# 前 60 分钟预留 32GB(启动阶段),60 分钟后降到 4GB(进入稳定阶段) bsub -R "rusage[mem=32000:duration=60,mem=4000]" run_sim.sh |
⚠️ 注意:这里有一个 EDA 工程师非常容易理解错的细节:duration=60 指的是“这一阶段资源预留(reservation)的生命周期”,不是“作业运行 60 分钟后会被杀掉”。过了 60 分钟,LSF 只是切换到下一阶段的预留数值(比如例子里降到 4GB),并不会因为到了这个时间点就终止作业——真正会终止作业的是前面讲的 MEMLIMIT/RUNLIMIT 这类运行时上限,duration 只影响调度层面的资源预留数量,两者不要混淆。
***多阶段 rusage 有一个容易忽略的限制:除最后一段外,前面每一段都必须显式指定 duration(分钟);而且多阶段预留不支持“递增型”(后一阶段比前一阶段申请更多)的写法,只能是维持不变或者逐阶段递减,这是 LSF 底层调度算法的限制——所以上面例子特意设计成 32GB 降到 4GB(递减),如果你的场景真的是“后期需求比前期更大”,多阶段 rusage 这个机制并不适用,需要用别的方式处理。
4.7 GPU 资源:自定义 Numeric 资源 vs LSF 原生 GPU 支持
近几年 IC 设计里 AI 辅助验证、机器学习加速仿真逐渐普及,GPU 也成了 CAD Farm 里需要精细管理的稀缺资源。前面讲的自定义 Numeric 消耗型资源确实可以用来管理 GPU 数量,配置思路和 license 完全一致:
# lsf.shared Begin Resource RESOURCENAME TYPE INTERVAL INCREASING CONSUMABLE DESCRIPTION ngpus Numeric () N Y (可调度GPU数量) End Resource
# lsf.cluster.fasteda_farm Begin ResourceMap RESOURCENAME LOCATION ngpus (4@[cadgpu01] 8@[cadgpu02]) End ResourceMap
# lsb.queues Begin Queue QUEUE_NAME = ai_verify RES_REQ = rusage[ngpus=1] RESRSV_LIMIT = [ngpus=1,2] # 单个作业最多申请2块GPU End Queue |
⚠️ 注意:这个自定义 ngpus 方案本质上只回答了“我有几块 GPU”这一个问题,管不了 GPU 具体分配给作业哪个 device、GPU 型号是否匹配、显存是否够用、多卡之间有没有 NVLink 直连、GPU 运行模式(独占/共享)等更精细的问题。如果 CAD Farm 里 GPU 只是零星几块、场景简单,自定义资源作为入门方案没问题;但只要涉及真正的 GPU 调度需求,都应该优先使用 LSF 10.1 内置的原生 GPU 资源管理,而不是继续在自定义资源上“造轮子”。
LSF 10.1 提供了专门的 bsub -gpu 选项,用来表达比“数量”复杂得多的 GPU 需求(提醒一下版本要求:-gpu 选项是 LSF 10.1 才引入的能力,如果集群还在用更早的版本,这个选项不可用,只能用前面讲的自定义资源方案,或者考虑升级版本):
# 申请2块GPU,独占模式,要求同型号,每卡预留10GB显存 bsub -gpu "num=2:mode=exclusive_process:gmem=10G" run_ai_verify.sh
# LSF 会自动把 num=2 转换成等价的 rusage[ngpus_physical=2] # 可以用 bjobs -l <JOBID> 查看合并后的实际资源请求 | ||
GPU 请求选项 | 作用 | |
num | 申请的物理 GPU 数量,LSF 会自动转换成 rusage[ngpus_physical=num] | |
mode | GPU 运行模式:shared(共享)或 exclusive_process(独占进程) | |
gmodel | 指定 GPU 型号(品牌+代号),如 TeslaK80,也可以进一步限定总显存大小 | |
gmem | 为作业在每块 GPU 上预留的显存大小,支持 M/G/T 单位 | |
glink | 要求分配的多块 GPU 之间具有高速互联(NVIDIA 的 NVLink 或 AMD 的 xGMI) | |
j_exclusive | 是否要求作业独占整块 GPU,不与其他作业共享 | |
***查看主机上的 GPU 可用情况可以用 lsload -gpuload、lshosts -gpu、bhosts -gpu 这几个命令,都是 LSF 原生 GPU 管理自带的查看工具,不需要像自定义资源那样额外写脚本采集。如果你的 CAD Farm 已经或计划引入 GPU 计算节点,建议直接规划原生 GPU 资源管理,本文受篇幅限制不展开完整配置,仅在这里指出正确的方向。
五、配置完了怎么证明真的生效
讲了这么多配置,实际运维中最容易被忽视的一步是:改完之后怎么证明配置真的按预期生效了,而不是只看 reconfig 没报错就当作万事大吉。下面这张表按“资源定义 → 映射 → 队列引用 → 作业实际状态”的顺序,给出每一层对应的验证命令。
验证目标 | 命令 | 作用 |
配置语法 | badmin ckconfig -v / lsadmin ckconfig -v | 改完任何配置文件后的第一步,检查语法和引用是否正确 |
资源定义 | lsinfo -l <资源名> | 确认 lsf.shared 里的资源定义(类型、说明等元数据)是否生效 |
静态资源分布 | lshosts -s | 查看静态 Shared 资源在各主机上的分布和总量 |
动态资源当前值 | lsload -l 或 lsload -s | 查看动态负载指标 / 动态 Shared 资源的当前实时数值 |
队列配置 | bqueues -l <队列名> | 确认队列的 RES_REQ、RESRSV_LIMIT 等参数是否按预期生效 |
Resource Limit 配置 | bresources -c | 查看 lsb.resources 里配置的资源预留策略(resource reservation)等定义是否正确加载 |
主机可用资源余量 | bhosts -a | 查看各主机上消耗型资源当前的可用余量,排查“资源明明还有、作业却 PEND”时很实用 |
资源配额占用 | blimits -a | 查看所有 Resource Allocation Limit 规则当前的占用/上限情况(USED/LIMIT),即使当前没有作业在用某条规则也会显示,比只查单条规则更适合用来核实配置是否真的加载了 |
作业实际请求 | bjobs -l <JOBID> 或 bjobs -al <JOBID> | 查看某个作业实际被 LSF 合并、认定的资源请求,用于排查申请量是否符合预期 |
✅ 最佳实践:排查“作业为什么一直 PEND”这类问题时,建议按上表从上到下走一遍:先确认资源定义和映射本身没问题,再看队列配置,再用 blimits 检查是不是撞到了配额上限,最后用 bjobs -l 确认作业自己申请的资源量是否合理——大多数“资源明明够用、作业却不跑”的问题,最后都能在这几条命令的输出里找到答案。
六、完整实战:20 个 Calibre license 的全链路管理
最后用一个贯穿本篇所有知识点的完整案例收尾——公司买了 20 个 Calibre DRC/LVS license,怎么从“定义资源”一路配置到“控制单用户、单项目的使用上限”,把本篇讲的 ResourceMap、RESRSV_LIMIT、lsb.resources Limit 三层机制串成一个完整的治理体系。
# 第一层:lsf.shared 定义资源元数据 Begin Resource RESOURCENAME TYPE INTERVAL INCREASING CONSUMABLE DESCRIPTION calibre_dv Numeric () N Y (Calibre DRC/LVS License) End Resource
# 第二层:lsf.cluster.fasteda_farm 用 ResourceMap 拆分总量,回答“全局有多少、分给谁” Begin ResourceMap RESOURCENAME LOCATION calibre_dv (15@[dig01 dig02 dig03 dig04] 5@[ana01 ana02]) End ResourceMap
# 第三层:lsb.queues 用 RESRSV_LIMIT 限定单个作业的申请范围,回答“单个作业最多拿多少” Begin Queue QUEUE_NAME = drc_lvs HOSTS = dig01 dig02 dig03 dig04 RES_REQ = rusage[calibre_dv=1] RESRSV_LIMIT = [calibre_dv=1,4] End Queue
# 第四层:lsb.resources 用 Limit 段限定单用户总占用,回答“单个人最多同时拿多少” Begin Limit NAME = CalibrePerUser PER_USER = all RESOURCE = [calibre_dv,4] End Limit |
最终形成的治理结构是:20 个 license 里,15 个分给数字组主机池、5 个分给模拟组主机池(ResourceMap 资源池隔离);数字组内任何一个作业最多申请 4 个(RESRSV_LIMIT 单作业上限);同时任何一个用户,不管开了多少个作业,加起来最多同时用 4 个(lsb.resources 单用户配额)——三层叠加,既防止了单个作业“包圆”资源,也防止了单个用户开很多小作业变相“包圆”资源。
⚠️ 注意:最后提醒一个前面文章也强调过的边界:这一整套治理体系,管的是“通过 LSF 提交的作业”。如果 EDA 工具允许工程师绕开 bsub 直接在命令行本地启动、直接向 license server 申请授权,LSF 的账本(不管是静态记账还是动态 ELIM 采集)都无法感知这部分占用,账本和 license server 的真实状态就会脱节。彻底杜绝的办法只有两个:一是从制度上强制要求所有相关工具必须通过 LSF 提交;二是投入成本做动态 ELIM,让 LSF 的资源数值直接来自 license server 的实时查询,而不是自己维护一份可能失真的账本。
七、常见问题与下篇预告
7.1 常见问题
• 配置了 ResourceMap,但 lsload -s 查不到这个资源?—— 先确认 lsf.shared 里 Resource 段的名字和 ResourceMap 里完全一致(大小写敏感),再确认已执行 lsadmin reconfig + badmin mbdrestart;另外注意静态资源用 lshosts -s 查看,动态资源才用 lsload -s/-l 查看,两者不要用错命令。
• RESRSV_LIMIT 配置了却没生效,作业还是能申请超量资源?—— 检查该资源在 lsf.shared 里是否被正确标记为 CONSUMABLE=Y;同时确认 lsb.modules 里 schmod_limit 插件已启用(回顾第一批文章里 lsb.modules 那一篇)。
• 同一个资源想给多个项目组分别设置独立配额,一定要用 ResourceMap 拆分吗?—— 是的,ResourceMap 是“资源池隔离”配额的方式,拆分后的两个池子完全独立、互不借用;如果不想资源池隔离、只是想统计和限制“每个用户/项目最多能用多少”,应该用 lsb.resources 的 Limit 段(本篇第四章已详细介绍),两者可以配合使用,也是本篇第六章完整案例采用的思路。
• Boolean 资源和“值为0/1的Numeric资源”有什么区别?—— Boolean 语义上表达“有没有”,主要用于 select 筛选主机;Numeric 即使只有0/1两个值,也能参与数值比较(如 >、<)和 rusage 预留,两者不能完全互换,按语义选择合适的类型。
• 为什么我的作业申请了 rusage[calibre_dv=8],超过 RESRSV_LIMIT 的上限 4,作业却不是被“自动改成4”而是直接失败?—— 这是 LSF 的标准行为,官方文档明确超出 RESRSV_LIMIT 范围的申请会被直接拒绝,不存在自动钳制到边界值的机制,需要工程师自己改小申请量重新提交,具体参见本篇 4.2 节。
7.2 本篇小结
这篇讲清楚了 LSF 资源体系的分类维度(Boolean/Numeric/String 与 Static/Dynamic 与 Host-based/Shared,三组维度互相独立),lsf.shared Resource 段怎么定义资源元数据,lsf.cluster.cluster_name 的 ResourceMap 段怎么把资源精确映射到主机(并且区分了静态资源要写数量、动态资源不能写数量这个关键差异),以及最容易混淆的四种“资源限制”机制——主机负载阈值、RESRSV_LIMIT、lsb.resources Limit、运行时资源用量上限——各自的边界和职责。到这里,“资源怎么定义、怎么分配、怎么限量”这条链路已经完整了。
下篇预告:用户与资源限额实战
最后一篇《LSF 用户与资源限额实战:打造完整的 CAD Farm 权限与配额体系》,会把前两篇讲的集群管理员权限、资源阈值,和之前系列里讲过的 lsb.users、lsb.queues、lsb.resources 全部串联起来,给出一套完整的、可以直接套用的多项目组权限与配额设计方案,包含“如何限制某个用户能用的资源总量”“如何设计多级管理员体系”“如何给新入职工程师快速开通合理权限”等实战场景


网友留言: