灵活开发、高效训练、便捷部署不可兼得?这款国产框架表示都可以有

在近期举办的「WAVE SUMMIT 2020」深度学习开发者峰会上,百度飞桨总架构师于佃海提到:

「飞桨的迭代前进,离不开两个重要驱动轮:一个是产业实践的打磨,一个是用户体验的持续优化。两个驱动轮互相配合,给飞桨提供了持久、广泛的发展动力,驱动飞桨拥有一个最灵活易用的产业级深度学习框架。」

这两个驱动,对应到框架的设计,就是在确保高效的同时,做到灵活易用。基于这一指导思想,飞桨在编程界面上同时支持了命令式编程和声明式编程,即通常说的动态图和静态图。

两种编程方式各有优势,动态图类采用 “define-by-run” 的执行方式,写一行代码即可即时获得结果,在编程体验、调试便捷性等方面有绝佳的优势;而静态图采用 “define-and-run” 的方式,事先定义好整体网络结构再执行,能够对全局编译优化,更有利于性能的提升,也有利于模型的保存和部署,但在编程调试灵活性上有所欠缺。

如何兼顾这两种模式的优势,做到灵活编程、高效训练和部署,同时具备更统一的编程体验,是一个很大的挑战。

最新发布的飞桨核心框架 1.8 版本,带来了重磅更新,总结下来包括两点:
  1. 动态图性能更卓越,经过多个版本的持续深度优化,飞桨动态图的训练性能已经媲美静态图。

  2. 动静更加统一,完备实现了一键式动转静、动静混合编程,使动态图开发可以无缝衔接部署,并能通过静态图执行模式对部分模型实现进一步的训练加速。

这次重大更新意味着什么呢?

这意味着开发者可以采用动态图模式编写和调试 AI 模型,享受便捷灵活的开发体验;并可实现和静态图相当的高性能训练,并无缝衔接模型存储和部署应用。

通俗地讲,就是以后开发 AI 模型再也不用做选择题啦!经过持续地融合统一,使用飞桨动态图、静态图的优势均可得到充分发挥,自然灵活兼顾卓越性能,开发者可以更容易地写出优雅的代码,实现更高效的开发和训练、部署。

飞桨是如何做到动静统一,性能卓越的呢?接下来将为你揭晓。

动静统一,带来极致用户体验

飞桨框架的设计思想,是期望将深度学习计算的编程和内在表示保持一致,所以从用户界面上没有引入 Graph 等概念,直接以程序化的 “Program” 形式描述神经网络模型的计算过程,对应的用户开发和通用的编程体验更加接近。

从更好全局编译优化以及上线部署的角度考虑,飞桨提供了一种完备的内在描述 ProgramDesc,可以表达任意复杂的模型,并实现编译期和运行时的分离——这个意义上可与声明式编程范式即 “静态图” 相对应。

从 Python 普及度、以及更方便用户灵活调试的角度出发,飞桨从 2018 年底开始扩展支持使用 Python 原生控制流、即时执行的命令式编程范式,也就是大家通常讲的“动态图”。


其实对飞桨而言,基于编程一致的计算描述,向动态图的扩展,以及动静转换都是非常自然的。

在之前的版本,飞桨已经提供了 TracedLayer,可以将不含数据相关的控制流的动态图模型转成静态图模型。在 1.8 版本中飞桨提供了功能更强大、更通用的 ProgramTranslator,可以完备地将 Python 语法下的计算定义转译为 Program,从而和全局优化延时执行模式打通,并可实现模型结构存储和上线部署。

操作上非常简单,只需要在定义神经网络时添加一个装饰器,就可以将对应函数内部的所有定义,包括依赖数据的控制流实现,递归地转换为静态 program 执行。并且在这种模式下,可以灵活控制,实现动静混合编程。

下面通过一个示例讲述如何在一个 Layer 中添加装饰器。

class MNIST(fluid.dygraph.Layer):
    def __init__(self):
        super(MNIST, self).__init__()

        self.conv = Conv2D( 1, 20, 5)
        self.pool2d = Pool2D( pool_size=2, pool_stride=2 )
        self.pool_2_dim = 5 * 4 * 4
        self.proj = Linear(self.pool_2_shape, 10,
                      act="softmax")

    @declarative  # 添加装饰器,将forward函数内Layers递归地转为静态图的Program执行
    def forward(self, inputs):
        x = self.conv(inputs)
        x = self.pool2d( x )
        x = fluid.layers.reshape( x, [-1, self.pool_2_dim])
        x = self.proj(x)
        return x

可以看到,仅仅需要在最外层的 Layer 加上一个 declarative 装饰器,即可把动态图转成静态图进行执行,不需要在其他任何地方做修改。

如果需要部署模型,通过 ProgramTranslator 将动态图的模型转成静态图的 program 即可,示例如下:
     
import paddle.fluid as fluid
from paddle.fluid.dygraph import  to_variable

fluid.enable_imperative()
prog_trans = fluid.dygraph.ProgramTranslator()
model = MNIST()
in_np = np.random.random( [20, 3, 28, 28]).astype("float32")
in_var = to_vairbale( in_np )
out = model(in_var)
prog_trans.save_inference_model("./dy2stat_infer_model", fetch=[0])
   
借助 ProgramTranslator,通过执行一次 forward 运算得到一个静态图的 program。之后就可以复用之前静态图一键部署的逻辑进行部署上线。ProgramTranslator 功能新推,还将持续完善,欢迎持续关注飞桨更新动态。

总结一下,基于动静更加统一的飞桨框架,开发者可采用更灵活的动态图模式编程。

  • 训练加速,在最外层的 Layer 加上一个 declarative 装饰器,即可把动态图转成静态图进行训练,通过全局优化可对部分任务(例如 RNN 类模型)明显提升训练性能。

  • 部署便捷,通过 ProgramTranslator 将动态图的模型转成静态图的 program,即可实现模型的静态存储和部署应用。

这将大大提升开发者的开发体验。
   
性能卓越,动态图媲美静态图的训练性能 

其实,对于大部分任务而言,无需通过动静转换,飞桨的动态图训练已经具备非常高的性能。飞桨自 1.3 版本版增加动态图功能以来,持续数个版本,一直致力于提升训练的整体性能。目前在主流的任务上,飞桨动态图执行模式已经能够达到与静态图媲美的水平。

以上数据对比了几个主流模型在单张 NVIDIA V100 GPU 配置上的训练数据,测试环境如下:

  • Ernie:基于 Wikipedia 数据集,配置 batch_size 50, seqlen256;

  • Resnet50:基于 imagenet 数据集,配置 batch_size 128;

  • Transformer base:基于 iwslt14 de-en 数据集,配置 max_token = 4096;

  • Mobilenetv2:基于 imagenet 数据集,配置 batch_size 256。


可以看出同模型在动态图模式下的训练速度与静态图相当。

这主要得益于以下层面的优化。

1.降低框架开销(overhead)

框架的开销可以认为是任务训练的总时间减去 op kernel 计算的总时间,框架开销越小越好。对动态图的即时执行模式而言,由于频繁交互而又没有全局优化,框架开销对训练效率影响很大,特别是对于 RNN 这类任务。飞桨通过执行流程和数据结构优化来降低框架的 overhead:
  • a) 减少 Python 与 C++ 交互复杂数据结构开销。由于 Python 和 C++ 端使用的数据结构不一致,会引入一次开销比较大的转换,通过优化流程,避免这类数据结构转换。

  • b) OP 执行优化。每个 OP 执行时,均需要做一次数据构造,OP 运行结束之后进行析构。通过优化 C++ 端的执行流程,简化数据结构,降低整体的 overhead。

  • c) 移除非必需的属性。在静态图时,每个 OP 需要引入一类属性对 OP 进行标记,这些属性的构造和析构耗费比较多的时间,但对于动态图不是必须的,通过移除属性的构造和析构,减低框架 overhead。

2.引入多流异步的数据加载方案

数据加载的性能会严重影响整个训练的吞吐。动态图模式下每个 OP 执行,都会申请 Python 的全局锁(Global Interpreter Lock),这将导致异步 DataLoader 中数据处理的线程效率受到很大影响。如果训练时每个 batch 的数据量比较大,DataLoader 的性能就不如静态图下那么高效。为此,飞桨动态图引入了多线程数据处理流程,多线程不会受到全局锁的影响,进而提升执行效率。

3.更优的 cache 机制

在卷积(Convolution)运算中,可根据输入的 shape、dtype、data_format 等信息选择最优的 cudnn algorithm,由此来提升执行效率,但是搜索最优的 cudnn algorithm 会耗费比较多的时间。

通过引入 algorithm 的 cache 机制,可以降低搜索的耗时,提升任务的整体训练效率。用户仅需要设置 FLAGS_cudnn_exhaustive_search = 1 即可开启这种 cache 机制。通过开启该机制,语音的 waveflow 任务性能可提升 200%。

除了以上优化,最新的 1.8 版本在动态图功能上也有进一步增强,可以支持更多的任务,满足开发者多样化的需求,目前主流的模型都可以使用动态图进行实现。
    
1、支持 double grad 运算

在一些任务(如 GAN 相关的)中,强依赖梯度惩罚功能,在 1.8 版本提供了 double grad 的支持,方便任务的实现。double grad 简单的使用示例如下:
     
      with fluid.enable_imperative()    # 激活动态图模式
        x = fluid.layers.ones(shape=[1], dtype='float32')
        x.stop_gradient = False
        y = x * x

        # Since y = x * x, dx = 2 * x
        # 调用grad方法                
        dx = fluid.dygraph.grad(
                outputs=[y],
                inputs=[x],
                create_graph=create_graph,
                retain_graph=True)[0]

        z = y + dx    
        z.backward()      # double grad计算
        
2、Layer 支持 hook 功能

1.8 版本提供了 forward pre-hook 和 forward post-hook 的支持,方便组网的时候对 input 进行修改。

forward pre-hook 的使用如下所示, 通过 forward_pre_hook,可以将输入整体扩大两倍,同时也可以支持更复杂的一些 pre_hook 运算( 比如参数的 normalization 运算 ):

     import paddle.fluid as fluid
import numpy as np

# forward_pre_hook函数修改了layer的输入:input = input * 2
def forward_pre_hook(layer, input):
    # 改变输入值
    input_return = (input * 2)
    return input_return

fluid.enable_imperative()
    linear = fluid.Linear(13, 5, dtype="float32")

    # 注册hook
    forward_pre_hook_handle = linear.register_forward_pre_hook(forward_pre_hook)

    value0 = np.arange(26).reshape(2, 13).astype("float32")
    in0 = fluid.dygraph.to_variable(value0)
    out0 = linear(in0)

    # 移除hook
    forward_pre_hook_handle.remove()

    value1 = value0 * 2
    in1 = fluid.dygraph.to_variable(value1)
    out1 = linear(in1)

同时飞桨已经开源了主流模型的动态图实现,不要错过这个丰富的资源库:
https://github.com/PaddlePaddle/models/tree/develop/dygraph

Ernie 也开放了动态图的实现,赶快来试试吧:
https://github.com/PaddlePaddle/ERNIE

更多飞桨动态图的应用方法,欢迎访问:
https://www.paddlepaddle.org.cn/tutorials/projectdetail/386189

如果您加入官方 QQ 群,您将遇上大批志同道合的深度学习同学。
飞桨官方 QQ 群:703252161。

如果您想详细了解更多飞桨的相关内容,请参阅以下文档。

官网地址:https://www.paddlepaddle.org.cn

飞桨开源框架项目地址:
GitHub: https://github.com/PaddlePaddle/Paddle
Gitee:  https://gitee.com/paddlepaddle/Paddle
产业机器学习框架百度百度飞桨
相关数据
参数技术

在数学和统计学裡,参数(英语:parameter)是使用通用变量来建立函数和变量之间关系(当这种关系很难用方程来阐述时)的一个数量。

暂无评论
暂无评论~