WDL - 架构简介

WDL 是 Workflow Description Language的缩写,有时也写作 Workflow Definition Language,是美国 Broad Institute 推出的工作流描述语言。从开发者及开发者赋予的名字中,我们就能看出WDL是一个面向生物信息/基因组学领域的专业的工具。

经过几年的发展,WDL 已经是生信行业广泛接受的一种工作流标准,具有下面的优势:

  1. Human-readable
    WDL 作为一种为工作流领域定制的语言,和 Shell、Python 等通用的脚本语言相比,没有过多复杂的概念,对使用者的计算机技能要求不高,对于生信用户容易上手。

  2. Portable Workflow
    WDL 可以在多个平台执行,比如本地服务器、SGE 集群,云计算平台等,可以做到一次编写多处执行。

  3. Standard
    作为GA4GH支持的工作流描述语言之一,已经得到了众多大厂和行业协会的支持,形成了比较完善的生态。

在GATK4的best practice中,不再像以前那样给出每个步骤对应的代码,而是直接给出了官方使用的pipeline。这些pipeline采用WDL进行编写。

参考文档

WDL参考文档

Broad WDL 官方论坛

WDL项目仓库

OpenWDL

WDL-readthedoc

标准流程描述语言 WDL 阿里云最佳实践

在学习编写 WDL 的过程中,可以参考 Broad 官方的一些 GATK工作流 借鉴和学习 WDL 的用法。

WDL架构结构组件

WDL是一种流程编写语言,没有太多复杂的逻辑和语法,入门简单。首先看一个hello world的例子

1
2
3
4
5
6
7
8
9
10
11
workflow myWorkflow {
call myTask
}
task myTask {
command {
echo "hello world"
}
output {
String out = read_string(stdout())
}
}

对于一个WDL脚本而言,有以下5个重要的核心结构

  • workflow:工作流定义
  • task:工作流包含的任务定义
  • call:调用或触发工作流里面的 task 执行
  • command:task在计算节点上要执行的命令行
  • runtime:task在计算节点上的运行时参数,包括 CPU、内存、docker 镜像等
  • output:task 或 workflow 的输出定义

image

1. workflow (call )

每个脚本包含1个 workflow ,定义了一个可执行的流程,它由 call 调用的一系列 task 组成的。每个 taskworkflow 代码块之外单独定义。

task可以是一个模块化的一系列命令(这个特性非常重要,特别是在容器化环境下,有机会详细说),它可以复用。由call 语句调用在workflow block里面,task本身定义在外围,它可以import,强烈建议这样书写增加可维护性,这里先不展开说了。task调用的顺序及书写顺序不决定流程执行task的顺序,但撰写过程应该尽可能保持整个流程的可读性。

2. task (command & output )

task是一个WDL中一个基本的任务模块,可以在不同的 workflow 中通过 call 进行调用。一个task模块至少包含两部分 (command & output )。除了两个必须模块外,还有 runtime可以用于指定任务运行所需的资源、环境,还可以进行变量的定义。

  • task 代表任务,读取输入文件,执行相应命令,然后输出;
  • command 中对应的就是执行的命令,比如一条具体的gatk的命令;
  • output 指定task的输出值。
  • runtime task在计算节点上的运行时参数,包括 CPU、内存、docker 镜像等

可以将task理解为编程语言中的函数,每个函数读取输入的参数,执行代码,然后返回,command对应执行的具体代码,output对应返回值。在wdl中,DAG关系就是由output来确认的。

3. glob

glob 是指对 workflow 或 task 的输出,支持通配符匹配。

1
2
3
output {
Array[File] output_bams = glob("*.bam")
}

  • 使用场景:
    输出文件有多个,且文件名不确定
  • 使用方法:
    采用 glob 表达式,用 array 方式存储多个输出文件
  • 价值:
    输出结果支持通配符匹配,简化 WDL 编写,采用数组方式,方便并发处理

4. Call caching

Call caching 是 Cromwell 的一个很有用的高级特性,通过 task 的复用,帮助客户节省时间,节省成本。

  • 适用场景:
    输入和运行环境不变的情况下,复用之前 task 的运行结果
  • 命中条件:
    输入 + 运行时参数相同
  • 价值:
    复用之前的执行结果,节省时间,节省成本

5.批量计算 runtime

用于配置任务运行时的相关参数。
使用批量计算作为后端时,主要的 runtime 参数有:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
cluster:
计算集群环境
支持serverless 模式和固定集群模式
mounts:
挂载设置
支持 OSS 和 NAS
docker:
容器镜像地址
支持容器镜像服务
simg:
容器镜像文件
支持singularity 镜像
systemDisk:
系统盘设置
包括磁盘类型和磁盘大小
dataDisk:
数据盘设置
包括磁盘类型、磁盘大小和挂载点
memory:
所需的任务内存
cpu:
所需的计算核心数目
timeout:
作业超时时间
maxRetries:
指令允许定义在发生故障时可以重新提交流程实例的最大次数。

具体的参数解释及填写方法,请参考 Cromwell 官方文档

除此之外,还有一些其他的概念

  • runtime
  • parameter_meta
  • meta
    从官方版本45开始,Cromwell 使用批量计算作为后端,支持 glob 和 Call caching 两个高级特性。

6. 变量的定义

对应WDL的结构,变量也分为两层,task层和workflow层。
最基本的变量有两个,一种是 File,对应文件,一种是String对应的是字符。task层的变量可以引用workflow层的变量,也可以直接传参。下面我举个例子来说明一下:

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
workflow helloHaplotypeCaller {
File Ref
String Sample
call haplotypeCaller{
input:
RefFasta=Ref,
sampleName=Sample
}
}

task haplotypeCaller {
File GATK
File RefFasta
File RefIndex
File RefDict
String sampleName
File inputBAM
File bamIndex
command {
java -jar ${GATK} \
-T HaplotypeCaller \
-R ${RefFasta} \
-I ${inputBAM} \
-o ${sampleName}.raw.indels.snps.vcf
}
output {
File rawVCF = "${sampleName}.raw.indels.snps.vcf"
}
}

在workflow层,我们定义了两个变量,Ref和Sample,在call haplotypeCaller的过程中,我们用input语句将它们传给了task层的RefFasta和sampleName,这样的话,提升了传参的复用——只用给Ref和Sample两个变量传参,多个task都可以引用它们。而其它的变量则可以由输入文件一起传入。

变量的类型,主要有以下几种:

  • String
  • Int
  • Float
  • File
  • Boolean
  • Array[T]
  • Map[K, V]
  • Pair[X, Y]
  • Object

关于每一种变量的使用,以及 WDL 的更多使用技巧,请参考官方规范文档

以上就是关于WDL的基本架构与变量的内容,一个最基本的WDL文件就可以完成了,接来就要准备对应的输入与执行了,我们会继续介绍。

WDL架构执行逻辑

task 如何组装成 workflow

一个 workflow 里面包含多个 task,task 之前的串行或并行关系如何表达呢?主要有下面三种情况:

  • Linear Chaining
    image

  • Multi-input / Multi-output
    image
    第二种是多输入多输出的场景,一个 task 可以定义多个输入和输出,比如上面的例子,task B 有两个输出,作为 taskC 的输入。

  • Scatter-Gather Parallelism
    image

第三种场景是用于 task 的并发执行。如果一个 task 有多个样本需要并发处理,可以使用数组的方式将样本传入,然后使用 scatter 并发的处理每个样本,每个执行的单元称为一个 shard。所有的 shard 执行完成,则当前 task 执行完成,所有 shard 的输出,又作为一个数组,可以传递到下一个 task 处理。

输入参数如何传入

配置文件生成与填写

workflow 的输入,比如基因样本的存储位置、计算软件的命令行参数、计算节点的资源配置等,可以通过 json 文件的形式来指定。使用 wdltools 工具可以根据 WDL 文件来生成输入模板:

1
java -jar wdltools.jar inputs myWorkflow.wdl > myWorkflow_inputs.json

模板格式如下:

1
2
3
{
"<workflow name>.<task name>.<variable name>":"<variable type>"
}

当然,如果工作流不是很复杂,也可以按照上面的格式手写 input 文件。下面是一个 GATK 工作流的 input 文件的片段:
image

示例

image

task定义:UnmappedBamToAlignedBam

image

工作流解析
image

  • 整个 Workflow 由5个 task 组成
  • Task 之间通过 Linear Chaining 的方式组合
  • 每个 Task 是子 Workflow,由多个 Task 组合而成。也就是说 WDL - 支持嵌套,workflow 里面的任务,既可以是一个 task,也可以是一个完整的 workflow,这个 workflow 被称为sub workflow。更多关于嵌套的用法请参考官方规范文档。

wikipedia

-------------本文结束感谢您的阅读-------------