4. 资源需求声明:在 process 中设置 cpus、memory、time、disk
声明资源的写法,说白了就是告诉 Nextflow:“我这个任务需要多少家底”。
你想想看,如果不说清楚,调度器要么给太少,任务跑一半就被 OOM 杀掉;要么给太多,浪费集群资源,排队排到天荒地老。
我最初接触 Nextflow 的时候,就在这上面吃过亏。一个 BWA 比对任务,默认给 1 个 CPU,跑了一个通宵都没完。后来我才意识到,资源声明是性能调优的第一步。
4.1 四个核心参数:cpus、memory、time、disk
每个 process 都可以声明这四个参数。它们直接告诉调度器(比如 SLURM、SGE、Kubernetes)你需要多少资源。
| 参数 | 含义 | 常用单位 |
|---|---|---|
cpus |
CPU 核心数 | 整数,如 4、8、16 |
memory |
内存大小 | MB、GB |
time |
运行时长上限 | s、m、h、d |
disk |
本地磁盘需求 | MB、GB |
举个例子。一个典型的比对任务,你可以这样写:
process align_bwa {
cpus 8
memory '16.GB'
time '2.h'
disk '50.GB'
script:
"""
bwa mem -t ${task.cpus} ref.fa reads.fq > aligned.sam
"""
}
注意一点:在 script 块里,用 ${task.cpus} 来引用你声明的 CPU 数量。这样真实的 BWA 命令就知道该用多少个线程了。我见过不少新手直接写 -t 8,结果后面改成 4 核还得手动改脚本。
核心原则:声明的资源要和实际命令匹配。你声明了 8 核,但软件只用 1 个线程,那就白费了。
4.2 资源单位的正确写法
Nextflow 对单位要求很严格。写错了直接报错。
| 单位 | 写法示例 | 注意点 |
|---|---|---|
| MB | '1024.MB' |
数字 + 点 + 单位 |
| GB | '2.GB' |
不要写成 '2 GB' 或 '2GB' |
| 小时 | '1.h' |
支持 s、m、h、d |
| 分钟 | '30.m' |
m 表示分钟,不是月份 |
为什么不能写成 '2GB'?因为 Nextflow 的解析器要求数字和单位之间必须有一个点。我一开始也踩过这个坑,报错信息还不明显,找了半天才发现是多写了个空格。
另外,memory 和 disk 支持小单位:
memory '500.MB' # 半 G
disk '10.GB' # 10 G
避坑:我曾经把一个基因组组装任务写成 memory '500.GB',结果集群根本没有这么大内存的节点,任务一直排队。后来才发现是数据估错了。建议先在小样本上测一下真实内存消耗,再写死资源大小。
4.3 理解动态资源分配:省时又省钱
静态声明虽然简单,但有个问题:你写的资源是固定的。假如一个任务平均只要 4 GB,但你写死了 16 GB,调度器就会给你分配 16 GB 的节点。集群里大内存节点本身就少,排队时间会变长。
动态资源分配就是来解决这个问题的。核心就一个思路:先给少量资源,失败了再自动增加,直到成功或达到上限。
实现方式是用 errorStrategy 'retry' 结合 maxRetries 和 maxErrors,配合 memory 的翻倍策略。
看一个完整的例子:
process dynamic_mem_task {
memory { 2.GB * task.attempt }
time { 1.h * task.attempt }
errorStrategy 'retry'
maxRetries 3
maxErrors 5
script:
"""
your_command_here --mem ${task.memory.toGiga()}G
"""
}
这里的关键是 task.attempt。第一次执行时 attempt = 1,内存 = 2 GB。如果内存不够失败了,第二次 attempt = 2,内存变成 4 GB。第三次是 8 GB。最多重试 3 次。
我有个 RNA-seq 定量流程就用这个模式。大部分样本 4 GB 就够,偶尔遇到高表达基因多的样本会爆内存。动态重试让整个流程跑得非常稳,又不会浪费资源。
小技巧:对于 IO 密集型任务,你可以把 cpus 也做成动态的。比如 cpus { 1 * task.attempt },第一次跑 1 核,不够就加。不过 CPU 一般不会因为不够而失败,更多是内存不足,所以动态 CPU 用得少。
4.4 用 labels 做资源分组
当你写了几十个 process 之后,会发现很多任务的资源需求是类似的。比如比对、排序、标记重复这些,都可以归为一类。
这时候可以用 labels 来分组,然后在配置文件里统一指定资源。
先给 process 打标签:
process align_bwa {
label 'high_mem'
script:
"""
bwa mem ref.fa reads.fq > aligned.sam
"""
}
process sort_sam {
label 'high_mem'
script:
"""
samtools sort aligned.sam > sorted.bam
"""
}
然后在 nextflow.config 里统一配置:
process {
withLabel: 'high_mem' {
cpus = 8
memory = '16.GB'
time = '2.h'
}
withName: 'sort_sam' {
cpus = 4
}
}
用 withLabel 给同类任务统一配置,再用 withName 微调个别进程。我习惯把最通用的资源写到标签里,特殊任务再单独覆写。这样配置很干净,改一个地方就能影响一批进程。
4.5 知识结构速览
下面这张图能帮你把今天的内容串起来:
4.6 避坑与最佳实践
- 不要写死大数字:我以前觉得 "给大点肯定没错",结果 256 GB 的任务在只有 128 GB 节点的集群里永远跑不了。先测,再写。
- 注意单位写法:
'2.GB'不是'2GB'。这个点一定要有。我还见过有人写'2 gb',小写也不行。 - disk 不是存储盘:
disk声明的是工作目录需要的本地 scratch 空间,不是最终的输出存储。如果你的输出很大,记得配置scratch 'false'或者指向大的挂载盘。 - time 超时会被杀掉:如果任务实际跑超了声明的时间,调度器会发 SIGTERM 杀掉进程。建议声明的时间比预估值多 30% 作为余量。
- 动态重试的尝试次数不要太多:我一般设 3 次。超过 3 次还失败,多半是代码或者数据有问题,重试也没用。
- GPU 资源怎么办:Nextflow 原生不支持
gpus声明。但你可以通过自定义配置文件传给调度器。后面会有专门章节讲。