乐趣区

关于人工智能:OneFlow-如何做静态图的算子对齐任务

撰文|李响

1

前言

深度学习框架中模型的运行形式次要有动态图和动态图两种,动态图更易用,动态图性能更具劣势,OneFlow 习惯将它们称为 Eager 模式和 Graph 模式。

OneFlow 提供了 nn.Graph 模块,让用户能够用相似 Eager 模式的编程习惯,构建动态图训练测试。因而,须要同时保障 Eager 和 Graph 模式下算子行为和后果的正确性。

在之前的文章《深度学习框架如何优雅地做算子对齐工作》中,剖析了 Eager Ops 的自动测试流程,包含如何产生随机数据测试用例和 AutoTest 外围代码实现,AutoTest 框架能够很轻易移植到其它深度学习框架应用。

不过,本文的次要目标则是介绍 OneFlow 如何实现 Graph 模式下算子的测试工作。目前为止,OneFlow v0.7.0 曾经新增所有 Op 在 nn.Graph 上做动态执行的单测反对,自动化单测性能齐备。

文章中波及到的代码地位:

  • https://github.com/Oneflow-In…
  • https://github.com/Oneflow-In…

2

OneFlow 的 Graph 算子对齐概述

OneFlow 提供的 Eager 模式,用法与 PyTorch 对齐。所以在测试上,AutoTest 框架会随机出各种非法参数组合成的 Op,并基于数值和类型完全相同的输出 Tensor(PyTorch 和 OneFlow 各有一份)别离运行 PyTorch 和 OneFlow 的代码,来实现算子对齐工作。

此外,OneFlow 还提供了 Graph 模式,基于面向对象的编程格调,让相熟 Eager 开发的用户,只需改很少的代码,就能够高效应用动态图。比照 Eager 模式,Graph 模式不易调试,但性能更好,易于优化和部署。那么,如何自动测试 Graph 模式下的 Ops 就是重点须要关注的问题。

在具体介绍 Graph 单测之前,咱们先看一下 AutoTest 框架里 Graph 关上办法,上面是一个测试 matmul 算子的例子。基于 random_pytorch_tensor 办法结构了两个随机的 tensor,它们的维度别离是 [n, k] [k, m],这些维度的值都是随机生成的,AutoTest 框架参数的随机性都是基于 generators.py 中的 generator 基类实现的。

@autotest(check_graph = True)
    def test_flow_matmul_with_random_data(test_case):
        device = random_device()
        k = random(1, 6)
        x = random_tensor(ndim=2, dim1=k).to(device)
        y = random_tensor(ndim=2, dim0=k).to(device)
        z = torch.matmul(x, y)
        return z

通过调用 torch.matmul(x, y),自动测试框架会别离运行 Torch 和 OneFlow 的 matmul 算子,会查看 Eager 模式下 OneFlow 和 PyTorch 算子的前向和反向后果是否统一。值得注意的是,代码中 @autotest 装璜器的 check_graph 开关为 True,示意此时会并行地做 Graph 的单测。

3

Graph 模式下自动测试实现原理

在理解背景和应用办法后,这里介绍 Graph AutoTest 的实现思路。

3.1 AutoTest 流程介绍

在 Eager 的自动测试原理中,对于随机数据是如何产生的和 autotest() 装璜器的实现,在前文中有清晰的介绍。对于 AutoTest 框架外围流程实现,首先必须要关注用于 OneFlow 和 Pytorch 的算子对齐工作中的 GetDualObject 函数。

GetDualObject 函数会重写传入的原始 PyTorch 以及 OneFlow 对象的 __call__ 魔法函数,最初返回一个 DualObject 对象。这个过程中还蕴含跳过一些不须要关注的魔法函数,查看传入对象的属性是否非法,基于 nn.Module 和其它 API 默认参数的类型对 generator 继承类产生的随机数据绑定特定类型的工作( get_args 函数中实现)。此外,在代码中还有对 tensor 办法的特判,因为 tensor 办法的调用形式(通过 getattr)和 nn.modulenn.functional 不同(通过 __call__)。

基于上述流程,通过执行样例代码中的 torch.matmul(x, y),AutoTest 框架会通过调用 GetDualObject 函数生成 DualObject 对象,其中 torch 就能够了解为一个 DualObject 对象。最初执行 DualObject 对象,实现后果比照。自动测试流程中 Eager 算子对齐更多的细节也在前文中做了清晰介绍。

  • GetDualObject 函数实现:https://github.com/Oneflow-In…
    * DualObject 类对象实现在:https://github.com/Oneflow-In…

3.2 Graph 模式如何随同 Eager 模式做算子对齐

从下面的剖析中,能够大略总结出 AutoTest 的流程:生成随机数据、生成 DualObject 对象、执行 DualObject 对象和判断后果是否对齐。其中,在执行 DualObject 对象阶段,AutoTest 框架会并行地执行 OneFlow 算子的 Graph 版本,这样也就实现了 Graph 模式随同 Eager 模式做算子对齐的工作。此外,本节也梳理了 GetDualObject 函数中应该如何辨认须要动态(Graph)执行的对象。

在算子对齐工作中,存在 nn.modulenn.functional tensor 办法三种类型。这里先以 nn.Module 类型为例,剖析 Graph 模式随同 Eager 模式测试的代码,其余三种类型解决办法基本一致。代码执行程序如下

oneflow_eager_run_with_graph_check 中别离调用了 get_module_graph_testget_oneflow_eager_res,失去 Graph 和 Eager 模式的两个后果,最初查看是否对齐。

也就是说,对于一个测试 case,AutoTest 框架总共执行了 Pytorch、OneFlow Eager 模式和 OneFlow Graph 模式三种代码,来验证三种后果是否都对齐了。

咱们先来探索下 get_module_graph_test 这个接口,也就是如何失去 Graph 版本的计算结果。代码如下:

# NOTE(lixiang): When oneflow is of type nn.Module, build the following Graph for testing.
#   graph_train_oneflow: is a deepcopy of oneflow.
def get_module_graph_test(graph_train_oneflow, oneflow, *args):
    of_sgd = flow.optim.SGD(graph_train_oneflow.parameters(), lr=0.001, momentum=0.9,)
    graph_train_parameters_len = 0
    for param in oneflow._parameters.values():
        if param is not None:
            graph_train_parameters_len += 1

    class TestGraphOfModule(flow.nn.Graph):
        def __init__(self):
            super().__init__()
            self.test_module = graph_train_oneflow
            if global_backward and graph_train_parameters_len:
                self.add_optimizer(of_sgd)

        def build(self, *args):
            res = self.test_module(*args)
            forward_res = res
            if global_backward and graph_train_parameters_len:
                res = res.sum()
                res.backward()
            return forward_res

    return TestGraphOfModule()

其中 oneflow 是一个 nn.Module 对象,graph_train_oneflow 是它的深拷贝后果,次要是为了避免在测试算子的 inplace 版本时,对相应的 DualObject 对象值进行了批改,造成 Graph 的输出和 Eager 不统一导致测试后果对不齐的状况。

首先为了验证 Graph 的后向能够失常执行,结构了一个 Optimizer。在 __init__ 中复用 Eager 模式下的 nn.Module 对象后,在 build 中形容了 Graph 测试的计算过程,最终返回了 Graph 的实例。简略来说,就是结构一个适应所有算子的通用动态图模型。

在探讨如何结构动态执行代码计算 Graph 后果之后,辨认须要动态执行的对象也是须要优先解决的问题。oneflow_eager_run_with_graph_check 的残缺代码如下:

# NOTE(lixiang): Check if the results of eager and graph are equal when oneflow is of type nn.Module or functional.
def oneflow_eager_run_with_graph_check(oneflow, oneflow_args, oneflow_kwargs, testing_graph, verbose, *args):
    if testing_graph:
        graph_args, graph_kwargs = get_args_copy(oneflow_args, oneflow_kwargs)

        if isinstance(oneflow, flow.nn.Module):
            graph_train_oneflow = copy.deepcopy(oneflow)
            if not is_global():
                arg_device_type = "cpu"
                for arg in oneflow_args:
                    if flow.is_tensor(arg):
                        arg_device_type = arg.device.type
                graph_train_oneflow = graph_train_oneflow.to(arg_device_type)

        else:
            graph_functional_oneflow = copy.deepcopy(oneflow)

    oneflow_res = get_oneflow_eager_res(oneflow, oneflow_args, oneflow_kwargs, verbose)
    if testing_graph:
        if verbose:
            print("After running eager module or functional:", repr(oneflow),
            )
        find_check_module_func = True
        ignore_apis_list = ["tensor", "train"]
        test_g_res = []
        if isinstance(oneflow, flow.nn.Module):
            test_g = get_module_graph_test(graph_train_oneflow, oneflow, *args)
            if verbose:
                print("Run graph of module:", repr(oneflow))
                test_g.debug(3)
            # When testing module methods, kwargs are not considered.
            test_g_res = test_g(*graph_args)
            if verbose:
                print("The result after running graph module:", test_g_res,)
        elif oneflow.__name__ in ignore_apis_list:
            find_check_module_func = False
        # 1. "oneflow.nn.modules" not in oneflow.__module__: For avoid run nn.Module branch graph test, like fold op call Fold Module actually.
        # 2. inspect.isfunction(oneflow): Compared with the ordinary flow.xxx, oneflow.nn.modules.math_ops series op exist an extra layer of python wrapper.
        # 3. inspect.ismethod(oneflow) and "oneflow.nn.modules" in oneflow.__module__:  For op that only has Tensor.xxx method, and call oneflow.xxx actually, like masked_fill.
        elif (("oneflow.nn.modules" not in oneflow.__module__)
            or inspect.isfunction(oneflow)
            or (inspect.ismethod(oneflow) and "oneflow.nn.modules" in oneflow.__module__
            )
        ):

            test_g_res = get_functional_graph_res(
                graph_functional_oneflow,
                oneflow,
                oneflow_res,
                oneflow_args,
                oneflow_kwargs,
                verbose,
                *graph_args,
                **graph_kwargs,
            )
        if find_check_module_func:
            if isinstance(test_g_res, tuple):
                for _, g_res in enumerate(test_g_res):
                    check_eager_graph_tensor(oneflow_res, g_res)
            else:
                check_eager_graph_tensor(oneflow_res, test_g_res)
    return oneflow_res

oneflow_eager_run_with_graph_check 中,须要判断哪些对象须要动态执行测试。因为 OneFlow 设定局部代码须要动态化,比方有些 Eager 模式下的办法,在 Graph 模式下没有定义。

下面的代码中首先通过 if testing_graph: 判断是否关上了 Graph 开关,既是否须要并行的做 Graph 的单测;再对 oneflow 对象的类型做 isinstance 判断,当为 nn.Module 时才须要动态执行,调用 get_module_graph_test。否则调用 get_functional_graph_res 等解决,在测试框架中其余须要相似判断的中央也同理。

 if testing_graph:
        ···
        ···
        if isinstance(oneflow, flow.nn.Module):
            ···
            test_g = get_module_graph_test(graph_train_oneflow, oneflow, *args)
            ···
        elif:
            ···
            ···

3.3 Graph 模式的自动测试个性化

在 3.2 介绍了 Graph 如何随同 Eager 模式做算子对齐的工作之后,本节次要剖析 Graph 模式自动测试的个性化内容。

在 Graph 模式下,须要解决 nn.modulenn.functionaltensor 三个类别的办法,AutoTest 框架采纳先判断后构图的形式。

首先,GetDualObject 函数中,相干的接口包含:get_pytorch_oneflow_res
get_pytorch_oneflow_tensor_res
oneflow_eager_run_with_graph_check
oneflow_tensor_eager_run_with_graph_check
get_oneflow_eager_resget_tensor_graph_resget_functional_graph_res get_module_graph_test。看一下每个接口的性能,如下表。

理解每个函数性能之后,再来看一下调用链,如下流程图所示,图中蕴含了 AutoTest 框架中对于 Graph 模式存在 nn.modulenn.functional tensor 三个类别的办法如何解决,对应图中的三个灰色框。

在剖析了 nn.modulenn.functionaltensor 三个类别的解决办法之后,其中,自动测试 Graph 时也存在一个反向的梯度测试,然而并没有取出 tensor 对应的梯度,也就是说,能够保障后向执行是失常的,没有查看 grad 值。

对于应用办法,当 @autotest() 关上 auto_backward=True 时(默认就是关上的),不仅会跑 Eager 的 Backward 测试(这里会对梯度后果做比拟),还会跑对应 Graph 的 Backward 测试(这里不做梯度比拟)。

对应上述形容的代码,能够在文章 3.2 局部的代码中找到:

if (
  global_backward
  and graph_train_parameters_len
):
  self.add_optimizer(of_sgd)
···
···
···
if (
  global_backward
  and graph_train_parameters_len
):
  res = res.sum()
  res.backward()

此外,对于一些算子 inplace 版本的 Graph 查看,须要对输出做深拷贝,来保障 Graph 和 Eager 的 input 始终统一。如下代码中,get_args_copy(在 torch_flow_dual_object.py 中)别离对一般参数和关键字参数做了 deepcopy。

相似的,在 Graph 单测中,存在 oneflow 深拷贝为 graph_train_oneflow 的行为,次要为了避免在测试一些算子时,Eager 的值被 Eager Inplace 批改,造成 Graph 的输出和 Eager 不统一导致测试出错的状况。

# NOTE(lixiang): Deepcopy the input parameters in order to correctly test the inplace version of the op.
def get_args_copy(args, kwargs):
    copy_args = []
    for arg in args:
        if flow.is_tensor(arg):
            copy_arg = arg.clone().detach()
        else:
            copy_arg = copy.deepcopy(arg)
        copy_args.append(copy_arg)
    copy_kwargs = {}
    for key, value in kwargs.items():
        if flow.is_tensor(value):
            copy_kwargs[key] = value.clone().detach()
        else:
            copy_kwargs[key] = copy.deepcopy(value)
    return copy_args, copy_kwargs

最初,为了保障 tensor deepcopy 的正确性,在 OneFlow 中,copy.deepcopy 会调用 tensor 的 getStatesetState 办法,tensor 的 state 须要同时包含 data、dtype 和 device 信息,缺一不可。具体代码见:
https://github.com/Oneflow-In…

4

Graph 的 Debug 反对

在 3.2 的代码中,能够发现存在 if verbose: 的判断,当 verbose = True 时,会输入 Graph 的 debug 信息(如算子运行 Graph 模式后的计算结果等),当然也包含 eager 下的其余须要的调试信息。

当测试呈现问题时,能够通过该性能拿到谬误样例,结构最小复现代码。开启办法通过环境变量管制:ONEFLOW_TEST_VERBOSE = 1。AutoTest 框架里这个性能更多针对开发者,OneFlow 的 Graph 针对用户也提供了调试性能。

Graph 模式反对了学习率的调试输入,开启办法和 Eager 雷同。

optimizer = flow.optim.SGD(model.parameters(), lr=1e-3)
# Set verbose=True
scheduler = flow.optim.lr_scheduler.CosineDecayLR(optimizer, decay_steps=100, alpha=0.98, verbose=True)

此外,调用 Graph 对象的 debug 办法,就开启了 Graph 的调试模式。

graph.debug(v_level = 1) # 能够简写为:graph.debug(1)

* v_level=0 时,只输入最根底的正告和构图阶段信息,如构图工夫。

  • v_level=1 时,将额定打印每个 nn.Module 的构图信息。
  • v_level=2 时,在构图阶段,将额定打印每个 Op 的创立信息,包含名称、输出内容、设施和 SBP 信息等。
  • v_level=3 时,将额定打印每个 Op 更具体的信息,如与代码地位无关的信息,不便定位代码问题。
    这部分更具体的内容能够在
    https://docs.oneflow.org/mast… 中发现。

5

总结

AutoTest 框架的灵活性和易用性都比拟强,本文次要介绍了 Graph 模式如何随同 Eager 模式做算子对齐和 Graph 的自动测试个性化内容。Eager 到 Graph 的 Local ops 执行测试笼罩也曾经在 OneFlow v0.7.0 中实现,在 0.8 版本中将会保障 Graph Global ops 单测的正确性。此外,动态图的 debug 和其余性能等将更齐备。欢送大家学习或者应用。

相干链接

https://github.com/Oneflow-In…
https://github.com/pytorch/py…

欢送下载体验 OneFlow v0.7.0 最新版本:

https://github.com/Oneflow-In…

退出移动版