Nextflow 进程详解:输入、输出、脚本、标签、资源分配
进程(process)是 Nextflow 的原子单位。
说白了,你的每个计算步骤——数据清洗、特征提取、模型训练——都会封装进一个进程中。
我刚开始用 Nextflow 的时候,总觉得进程就是写个脚本跑一跑。
后来项目复杂度上去了才发现,输入/输出的声明方式、资源分配的策略,直接决定了管线能不能稳定跑完。
这一节咱们就逐一拆解进程的五个核心要素。
输入:管线的「入口」
每个进程都需要指定 input 块。
它告诉 Nextflow:这个进程要消费什么数据。
我个人习惯用 set 输入组合多个值。比如同时传一个文件和一个参数:
process FEATURE_EXTRACT {
input:
path fasta_file
val cutoff
}
为什么这么写?
因为有时输入的通道不一定是文件,可能是列表或值。
path 用来处理文件,val 用来处理普通值。
这里有几种常用的输入声明方式,供你参考:
| 输入类型 | 关键字 | 适用场景 |
|---|---|---|
| 文件 | path | 读取 FASTA、CSV、模型文件等 |
| 标量值 | val | 整数、字符串、参数列表 |
| 环境变量 | env | 传递 PATH、TOKEN 等系统变量 |
| stdin | stdin | 管道进程之间的流式传数据 |
输出:结果的「通道」
输出声明看起来有点吓人,但核心逻辑很简单——告诉 Nextflow 这个进程会产生什么文件。
我见过很多新手在 output 块里漏写文件,结果后续进程一直报「找不到文件」。
嗯,这里一定要注意:你声明了才能传出去。
process COUNT_KMERS {
input:
path reads
output:
path "counts.tsv", emit: kmer_table
script:
"""
kmer_counter --input $reads --output counts.tsv
"""
}
emit: kmer_table 这个标记非常实用。
它给输出通道取了个名字,后续进程可以直接引用:
workflow {
COUNT_KMERS(reads_ch)
NORMALIZE(kmer_table) // 直接用名字引用,不用管变量名
}
我在项目中遇到过一种情况:输出文件不确定 —— 有时输出 3 个,有时输出 10 个。
这时候可以用 path "*" 来通配匹配:
output:
path "*.json"
脚本:核心执行逻辑
script 块就是写实际命令的地方。
你可以写 bash、Python、R——只要是 shell 能跑的,Nextflow 都能接管。
我建议把脚本写得尽量干净。不要在里面嵌入复杂的流程控制,否则调试日志会乱成一团。
script:
"""
feature_engineer.py \
--input $input_fasta \
--method pca \
--output $output_file
"""
为什么用三引号?
因为 Nextflow 的 script 块接受多行字符串,用单引号会导致字符串里的变量不会被解析。
用双引号则可以让 $input_fasta 这种变量被替换。
有一种特殊情况——你想在原样字符串里保留 $,比如传给 shell 的变量:
script:
"""
echo "Shell 变量:\$HOME"
"""
转义一下就好,我当年吃了不少这个坑。
标签:模块化与复用
label 是 Nextflow 的一个小功能,但非常实用。
它可以给进程打上语义标签,比如 "gpu"、"heavy"、"fast"。
为什么要用标签?
因为你可以基于标签做资源路由、日志分类、甚至条件执行。
process DEEP_LEARN {
label 'gpu'
label 'training'
...
}
我曾经历过一个管线,后期要加入一个新特性,结果发现旧进程的参数全用 old_ 前缀标注着。
如果用标签,甚至可以一键筛选出所有 'deprecated' 进程进行替换。
资源分配:让计算恰到好处
Nextflow 默认不会给你分配很多资源。
如果你不声明,它可能只用 1 个 CPU 和 1 GB 内存跑你的模型——当然会慢得让人怀疑人生。
资源分配主要通过 cpus、memory、time 这几个指令来控制:
process HEAVY_TRAIN {
cpus 8
memory '32 GB'
time '2h'
script:
"""
python train.py --cpus 8
"""
}
注意:你声明了 cpus 8,但 Python 代码并不知道。
你需要在脚本里也把 --cpus 8 传进去,否则它只会用一个核。
我曾经用 cpus 1 跑一个需要 16 核的任务,结果跑了三天才跑完。后来加上了资源声明,几个小时就搞定了。
嗯,这东西看似简单,却是管线性能的分水岭。
资源分配的常见参数我列成表格,方便你查:
| 参数 | 作用 | 单位 | 建议值 |
|---|---|---|---|
| cpus | CPU 核数 | 整数 | 根据计算节点配置,通常 2~32 |
| memory | 内存上限 | GB / MB | 数据量和算法决定,谨慎设置 |
| time | 最长执行时间 | h / m / s | 防止死循环或数据错误导致的无限等待 |
| disk | 临时存储 | GB | 需要大 I/O 时设置 |
| queue | 指定队列名称 | 字符串 | 用于区分 GPU 队列、高内存队列 |
本章核心结构
下面这张 SVG 图把进程的五个要素串了起来。你看一眼,就能理解它们之间的关系。
综合示例
把上面这些要素组合起来,就是一个完整的进程:
process TRAIN_MODEL {
label 'gpu'
label 'training'
cpus 4
memory '16 GB'
time '3h'
input:
path train_data
path config_file
val epochs
output:
path "model.pt", emit: trained_model
path "training.log"
script:
"""
python train.py \
--data $train_data \
--config $config_file \
--epochs $epochs \
--cpus ${task.cpus}
"""
}
这里用到了 ${task.cpus},它自动读取你声明的 cpus 4。
这样一来,资源声明和脚本里的参数就对齐了。不用手动重复写死数值。
task 对象里还有 task.memory、task.time 等属性,都能在脚本里引用,保持一致性。
避坑指南
最后总结几个我踩过的坑,希望能帮你绕过:
- 输入输出文件名字必须一致:脚本里生成的文件名,必须与 output 块里声明的完全一致。大小写、扩展名都不能错。
- 不要硬编码资源值:用
task.cpus和task.memory代替,方便后期调整。 - 标签不是注释:标签有实际作用——可以用在
withLabel选择器里做条件执行。 - time 别设得太紧:留 20%~30% 的余量。特别是数据量不可控时,容易超时失败。
进程是 Nextflow 的基石。
输入输出定义清晰、脚本干净、资源分配合理,管线就跑得稳、跑得快。
我个人觉得,花时间把这些基础打磨好,比后面堆砌复杂功能划算得多。