乐趣区

关于算法:动转静两大升级一键转静成功率领先重点模型训练提速18

目前支流深度学习框架反对的编程形式有两种,别离为动态图和动态图。动态图的 Pythonic 编程体验更佳、更易调试,但性能方面与动态图有肯定差距。动态图先组网再执行,事后领有残缺网络结构,更利于全局优化,虽调试难度大,但执行性能更佳。

百度飞桨采纳动静对立的技术架构设计,提供了动转静(@to_static)模块性能,反对用户动态图编程,并可一键切换动态图训练和部署。2022 年 11 月,飞桨框架 2.4 版本 (以下简称飞桨 v2.4) 正式公布,动转静“转换成功率”和“训练性能”迎来全面降级,带来了全新的用户应用体验。

  • 动转静成功率显著晋升,一键转换成功率达到 92.1%。
  • 动转静训练减速成果显著,重点模型训练可提速 18%+。

一键转静成功率显著晋升

动转静的转换成功率是动转静性能的一个重要指标,与用户的应用体验非亲非故,飞桨 v2.4 从“动转静语法欠缺”和“API 动静行为对立”两个方面进行了重点优化和降级:动转静语法欠缺

JIT 式动静执行

新增 Shape、Len、Attr、List、Unpack、Indexable 等 JIT 模式接口,晋升语法转写的鲁棒性。

控制流语法重构

重构了控制流 IF/For/While 语法转写逻辑,齐备反对简单嵌套场景下变量名解析等疑难问题。

关键字语法优化

优化了控制流中提前 return、break、continue 等关键字语法转写机制,无效缩小了动态图两头示意多余算子的引入,晋升执行效率。
API 动静行为对立

属性参数可变

实现了 20 多个中高频动态图 API 参数的降级,如

Reduce 系列的 paddle.mean/sum/max/min API 的参数 axis,新增反对为 Tensor 类型,动静可变。

接口动静对立

补齐了 Tensor 类动态图下缺失的接口,降级了 paddle.to_tensor、paddle.grad 等高频 API 性能,反对动态图调用。

Einsum 降级

实现了动静对立爱因斯坦求和算子,并反对 Python 二元、多元输出,训推一体。

动转静成功率和语法反对度 飞桨 v2.4 下,动转静具备了更丰盛的语法反对,Python 语法反对比例达到了 90%,在 80 多个内部用户实在论文复现模型汇合上,动转静一键转写成功率晋升至 92.1%,性能齐备性和易用性都有显著晋升。 如下是一个动转静导出预测模型的样例代码:

import paddle

class SimpleNet(paddle.nn.Layer):
    def __init__(self):
        super(SimpleNet, self).__init__()
        self.linear = paddle.nn.Linear(10, 3)

    @paddle.jit.to_static   # step 1:增加装璜器
    def forward(self, x):
        out = self.linear(x)
        out = out + 1
        return out

net = SimpleNet()
train(net)  # 此处略去了训练过程

# step 2: 切换到 eval() 模式
net.eval()
# step 3: 调用 jit.save 接口
paddle.jit.save(net, path='./simple_net')

执行上述代码样例后,在当前目录下会生成三个文件,即代表胜利导出预测模型:

simple_net.pdiparams        // 寄存模型中所有的权重数据
simple_net.pdmodel          // 寄存模型的网络结构
simple_net.pdiparams.info   // 寄存额定的其余信息

动转静导出模型个别包含三个步骤:

  • 增加装璜器

将 @to_static 装璜器装璜在 forward 函数上。

  • 切换 eval 模式

如 Dropout、LayerNorm 接口在 train() 和 eval() 模式下行为存在较大的差别,在模型导出前,请务必确认模型已切换到正确的模式。

  • 调用 save 接口

调用 paddle.jit.save 接口导出其对应的模型文件和参数文件。飞桨动转静 @to_staitc 更多功能用法,可参考【扩大浏览—动转静应用样例】

动转静训练减速成果显著

在飞桨框架中,通常状况下应用动态图训练即可满足大部分场景需要。飞桨 v2.4 优化了动转静训练的相干逻辑,面向重点模型,动态图训练的性能曾经能够和动态图媲美,例如在 ResNet50、Transformer、YOLOv3 等模型上,动转静训练相较于动态图有 18.6%~21.5% 的显著减速成果。

重点模型减速成果 在如下场景中,开发者能够思考应用动转静形式进行模型训练,将会取得较显著的性能晋升成果。 场景一: 重调度模型

即每个 API 背地的 GPU Kernel 计算耗时较少,在 CPU 端拉起后很快就执行完了,此类工作的特点:

  • PU 利用率较低(可通过 watch -n 1 nvidia-smi 命令查看)。
  • 常见于 NLP 畛域或 AMP/FP16 工作。
  • 训练性能瓶颈点次要是 Host 端调度开销。

如上图是重调度模型的动态图和动转静 Timeline 示意图。从图中能够看出:

  • 一个 Batch 的训练耗时取决于 Host 端总耗时。
  • 动态图每个 Python API 在运行时,都会产生一次 Python 和 C ++ 交互,会产生较大的调度开销。
  • 动转静之后,整体上切分为执行前向和反向的两个 Python C API,故缩小了很多个 API 间的调度开销。
  • 动转静内核执行器也通过了极致的优化(如 Instruction 缓存等),Kernel launch 效率也会比纯动态图模式要高。

对于想应用动态图训练代码的用户来说,只须要在组网入口的 forward 函数处增加装璜器 @to_static,其余代码无需改变就能够一键切换为动转静训练。@to_static 装璜器会将此函数内的所有 subLayers 转化为一个动态子图并执行。如下是一个动转静训练样例代码:

import paddle

class SimpleNet(paddle.nn.Layer):
    def __init__(self):
        super(SimpleNet, self).__init__()
        self.linear = paddle.nn.Linear(10, 3)

    @paddle.jit.to_static   # 仅需一行代码
    def forward(self, x):
        out = self.linear(x)
        out = out + 1
        return out

# create network
net = SimpleNet()
adam = opt.Adam(learning_rate=0.001, parameters=net.parameters())

for batch_id, x in enumerate(data_loader()):
    out = net(x)
    loss = paddle.mean(out)
    loss.backward()
    opt.step()
    opt.clear_grad()

场景二:调度 + 计算共存模型 即模型训练时同时存在计算量小和计算量大的 GPU Kernel,且一个 Batch 的起始地位常为小 Kernel,此类工作的特点:

  • GPU 利用率稳定比拟大(可通过 watch -n 1 nvidia-smi 命令查看)。
  • 训练性能瓶颈点同时受部分调度和部分 Kernel 计算效率影响。

如上图是调度 + 计算共存的动态图和动转静 Timeline 示意图。从图中能够看出:

  • 一个 Batch 的训练耗时取决于 Max(Host 端,GPU 端)。
  • 动转静升高了 Python C API 调度开销,收益点大多在 Batch 前半部分,后半局部可能会被 overlap,调度方面的收益会打折扣。
  • 动转静可借助全局图优化技术,通过算子交融等技术晋升模型训练的吞吐。

在此种场景下,飞桨动转静 @to_static API 提供 build_strategy 参数,在动转静的全图视角下,用户能够通过 build_strategy 参数开启不同的全局图优化策略。通过装璜器 @to_static(build_strategy=get_build_strategy())或者 API 调用 paddle.jit.to_static(net, build_strategy=get_build_strategy())两种形式开启全局图优化策略,如下是一个简略的应用样例:

import numpy as np
import paddle
import paddle.nn as nn

def get_build_strategy():
    build_strategy = paddle.static.BuildStrategy()
    # 算子交融策略
    build_strategy.fuse_elewise_add_act_ops = True
    # 梯度 addto 策略
    build_strategy.enable_addto = True
    os.environ['FLAGS_max_inplace_grad_add'] = "8"
    return build_strategy

class ResNet(paddle.nn.Layer):
    # 此处省略了模型定义
    @to_static(build_strategy=get_build_strategy()) # 形式一
    def forward(self, image):
        # 省略前向代码

# create network
net = ResNet()
# 借助 build_strategy 参数自定义开启「全局图优化」策略
net = paddle.jit.to_static(net, build_strategy=get_build_strategy()) # 形式二

adam = opt.Adam(learning_rate=0.001, parameters=net.parameters())

for batch_id, image in enumerate(data_loader()):
    out = layer(image)
    loss = paddle.mean(out)
    loss.backward()
    opt.step()
    opt.clear_grad()

飞桨动转静 @to_static 开启更多全局图优化用法,可参考【扩大浏览—动转静训练图优化策略】

拓展浏览

[1]动转静应用样例 https://www.paddlepaddle.org.cn/documentation/docs/zh/guides/…

[2] 动转静训练图优化策略 https://www.paddlepaddle.org.cn/documentation/docs/zh/guides/…

[3]动转静转换原理 https://www.paddlepaddle.org.cn/documentation/docs/zh/guides/…

[4] 动转静报错调试 https://www.paddlepaddle.org.cn/documentation/docs/zh/guides/…

[5] 动转静 Limitationshttps://www.paddlepaddle.org.cn/documentation/docs/zh/develop…

退出移动版