cuDNN 库提供了一个声明性编程模型,用于将计算描述为操作图。Graph API 是在 cuDNN 8.0 中引入的,以提供更灵活的 API,尤其是在操作融合(operation fusion)日益重要的情况下。
用户首先构建操作图。在较高的层次上,用户描述张量操作的数据流图。给定最终的图(finalized graph),用户然后选择并配置可以执行该图的引擎(engine)。有多种选择和配置引擎的方法,这些方法在易用性、运行时开销和引擎性能方面需要权衡。
注:算子和操作在文中大部分情况下意思相同,都对应Operation。
Graph API 有两个调用方式:
- Backend Descriptor Types (lowest level entry point into the graph API)
- NVIDIA cuDNN Frontend API (convenience layer on top of the C backend API)
其中,cuDNN frontend API更加简单易用,并且在Github上开源,相当于对底层级的graph API做了warp。
关键概念
Operations and Operation Graphs
Operation Graphs是张量操作的数据流图。它是一种数学规范,并且与可以实现它的底层Engines分离,因为给定的图可能有多个Engine可用。
I/O 张量隐式地连接operation,例如,operation A 可能会产生张量 X,然后由operation B 消耗,这意味着operation B 依赖于operation A。
Engines and Engine Configurations
cuDNN Engines 是预编译的、针对特定深度学习操作优化的计算引擎,它们可以直接在 NVIDIA GPU 上运行,无需在每次调用时都重新编译或解释代码。这些Engines覆盖了深度学习中的常见操作,如卷积、矩阵乘法、激活函数等。通过使用这些Engines,开发者可以显著提高神经网络训练和推理的速度。
对于给定的Operation Graph,有一些Engines是实现该Graph的候选Engines。查询候选Engines列表的典型方法是通过启发式查询(heuristics query);Engine能够配置相关属性,比如tile size等。
Heuristics
启发式(heuristic)是一种获取引擎配置列表的方法,这些引擎配置旨在针对给定的操作图从性能最高到性能最低进行排序。有以下三种模式:
- CUDNN_HEUR_MODE_A - 旨在快速并能够处理大多数操作图模式。它返回按预期性能排名的Engines配置列表。
- CUDNN_HEUR_MODE_B - 旨在比模式 A 更准确,但会以更高的 CPU 延迟为代价返回Engines配置列表。在知道模式 A 可以做得更好的情况下,底层实现可能会退回到模式 A 启发式。
- CUDNN_HEUR_MODE_FALLBACK - 旨在快速并提供功能回退(fallback),而不期望获得最佳性能。
通常使用modeA或者modeB,找到的第一个支持的Engine预计会有最佳的性能。
Graph API Example with Operation Fusion
以下示例实现了卷积(convolution)、偏置(bias)和激活(activation)的算子融合,三个算子如下图所示。
1. Creating Operation and Tensor Descriptors to Specify the Graph Dataflow
第一步,需要创建相应的三个backend operation descriptors。
在上图中,指定了一个前向卷积算子、一个用于偏置加法的逐点(Pointwise)算子以及一个用于 ReLU 激活的逐点算子。
然后,需要为图中所有操作的输入和输出创建tensor descriptors。图数据流是通过张量的分配来隐含的,例如,通过指定后端张量Tmp0
既作为卷积运算的输出又作为偏置运算的输入,cuDNN推断数据流从卷积运行到偏置。这同样适用于张量Tmp1
。如果用户不需要中间结果 Tmp0
和 Tmp1
用于任何其他用途,则用户可以将它们指定为虚拟张量(virtual tensors),以便可以优化内存 I/O。
2. Finalizing The Operation Graph
第二步,确定operation graph。作为最终确定的一部分,cuDNN 执行数据流分析以建立操作之间的依赖关系并连接边缘,如下图所示。在此步骤中,cuDNN 执行各种检查以确认graph的有效性。
3. Configuring An Engine That Can Execute The Operation Graph
第三步,给定最终确定的操作图,用户必须选择并配置Engine来执行该图,从而产生执行计划(execution plan)。可以直接指定,也可以通过启发式寻找性能最优的Engine,执行启发式操作的典型方法是:
- 查询启发式模式 A 或 B。
- 寻找第一个具有功能支持的Engine config(或自动调整所有具有功能支持的引擎配置)。
- 如果未找到Engine config,尝试查询回退启发式。
4. Executing The Engine
最后,构建execution plan并在运行它时,应通过提供工作区指针(workspace pointer)、UID 数组和设备指针(device pointers)数组来构建后端变体包(backend variant pack)。 UID 和指针应按相应的顺序排列。有了handle, execution plan 和 variant pack,就可以调用执行API并在GPU上进行计算。
下一篇文章会分析这一过程的cuDNN frontend API代码实现。