• 如何在OneFlow中新增算子


    89e4b991f17fc3a79d68cbe0d59bde6f.jpeg

    撰文|姚迟郑泽康

    本文将以开发一个 leaky_relu(准确说是 leaky_relu_yzh op,因为 master 分支的 leaky_relu 组合了其它知识点)为例介绍如何在 OneFlow 中新增算子(https://github.com/Oneflow-Inc/oneflow/pull/8350)。

    1

    背景

    op 与 kernel

    op 与 kernel 是两个有关联的概念。op 是逻辑上的算子,包含 OneFlow Compiler 在构建计算图时所需要的必要信息,如输入、输出形状,哪些张量需要自动求导等信息。有了 op 中的信息,OneFlow Compiler 就可以构建计算图并依据计算图做资源申请、构建等操作(如根据张量的输入输出大小申请内存), 但是 op 中不包含具体的处理数据的逻辑。

    在真正需要处理数据时,OneFlow Runtime 会启动 kernel 完成计算,所以 kernel 中包含了具体处理数据的逻辑。对于一个逻辑上的 op,OneFlow  Runtime 会根据数据类型、硬件设备(比如是 CPU 还是 CUDA)的具体情况,选择启动不同的 kernel。

    OneFlow 中的系统 op 与 user op

    在 OneFlow 系统中存在两类算子(op):系统 op 和 user op。

    系统 op 定义在:oneflow/core/operator/ 目录, 对应的 kernel 实现在:oneflow/core/kernel 目录。系统 op 是对构图、流水等系统性能较为关键的一些 op。

    除极少数 op 属于系统 op 外,大多数 op 都是 user op,这些 user op 和用户模型业务逻辑相关。OneFlow user op 的定义及 kernel 实现分别在 oneflow/user/ops 和 oneflow/user/kernels 目录下。

    目前 OneFlow 已实现了丰富的算子库,但是当已有的算子库无法满足搭建模型的需求时,就需要新增算子。本文介绍的新增算子指的是新增 user op。

    ODS 与 TableGen

    TableGen(https://llvm.org/docs/TableGen/index.html) 是一个代码生成工具,简单而言,它读取并解析一个 .td 格式(语法接近 C++ 模板)的文件,然后交给 TableGen 后端

    https://llvm.org/docs/TableGen/BackEnds.html)生成另外格式的语言。

    MLIR 基于 TableGen 制定了一套算子定义规范ODS(https://mlir.llvm.org/docs/OpDefinitions/)以及对应的后端 OpDefinitionsGen(https://github.com/llvm/llvm-project/blob/main/mlir/tools/mlir-tblgen/OpDefinitionsGen.cpp。

    OneFlow 在 ODS 的基础上,实现了 TableGen OneFlow 后端(https://github.com/Oneflow-Inc/oneflow/tree/master/tools/oneflow-tblgen),并使用它来定义 OneFlow user op。

    因此,OneFlow 的 user op 定义写在 OneFlowUserOps.td 文件中。

    2

    开发 op

    在 OneFlow 中开发一个新的 user op,主要分为以下4步:

    1. 定义 op

    2. 实现 kernel 计算逻辑

    3. 导出 functional 接口

    4. 实现用于求导的反向逻辑

    定义 op

    定义 op 指的是,对 op 的名称,op 的输入、输出数据类型和 op 的属性进行声明。OneFlow 遵循 MLIR 的 ODS(Operation Definition Specification)(https://mlir.llvm.org/docs/OpDefinitions/) 实现了自己的 MLIR OneFlow Dialect。在算子定义方面,这样做的好处是,各种推导函数和序列化/反序列化的接口都可以委托给 ODS,降低了人工手写出错的概率,后续优化、格式转化等流程可以更灵活。

    定义一个 OneFlow user op,主要包括 5 个部分,分别是:

    • op class

    • 输入 input

    • 输出 output

    • 属性 attrs

    • 导出并实现推导接口

    op class

    可以在 oneflow/ir/include/OneFlow/OneFlowUserOps.td 查看 op 定义的源码。

    以 def 关键字开头定义一个 op,该 op 继承 OneFlow_BaseOp,同时指定 OneFlow_BaseOp 的模版参数。模版参数依次为 op type name、Trait (https://mlir.llvm.org/docs/Traits/)列表。

    1. def OneFlow_LeakyReluYZHOp : OneFlow_BaseOp<"leaky_relu_yzh", [NoSideEffect, DeclareOpInterfaceMethods]> {
    2. //...
    3. }

    其中 "leaky_relu_yzh" 是指定的 op type name。每个 op 都需要指定一个全局唯一的 op type name 作为全局标识符。

    第二个模板参数是一个 list([...]),其中的每一项都是一个 Trait,OneFlow 中常用的有:

    • NoSideEffect 表示该算子无副作用(即不会改变内存、网络、管道、磁盘等的系统状态),这个特性可以指导某些优化操作

    • NoGrad 表示该算子在数学上没有梯度(不可导)

    • CpuOnly 表示该算子只支持在 CPU 设备上执行

    • SupportNonContiguous 表示该算子是否支持 NonContiguous 张量(关于 Contiguous Tensor 的概念,可以参考 PyTorch Internals 中的相关内容 )

    输入 input 与输出 output

    通过重写 input 域来定义 op 的输入,比如

     
     
    1. // 一个输入 x
    2. let input = (ins
    3. OneFlow_Tensor:$x
    4. );

    定义了一个输入张量 x。输入的格式为 输入类型:$name。

    输入类型目前包括:

    • OneFlow_Tensor

    • Variadic:指可变 tensor,比如 concat op,支持 concat 可变个数的 tensor。

    • Optional:表示这个 tensor 是可选的,既可以有也可以没有,比如 conv op 中的 add_output。

    一个 op 也可以定义多个输入,比如:

     
     
    1. // 两个输入:a, b
    2. let input = (ins
    3. OneFlow_Tensor:$a,
    4. OneFlow_Tensor:$b
    5. );

    通过重写 output 域来定义 op 的输出,比如下面定义了 2 个输出张量:

     
     
    1. let output = (outs
    2. OneFlow_Tensor:$out0,
    3. OneFlow_Tensor:$out1
    4. );

    属性 attrs

    通过重写 attrs 域定义 op 的属性,比如定义 dropout (<

  • 相关阅读:
    两种头结构
    生命科学领域 - FAIR原则和如果使数据FAIR化
    图形化思维:Graphviz和DOT语言的艺术与实践
    微信小程序家政预约系统+后台管理系统
    Django(二、静态文件的配置、链接数据库MySQL)
    学习潘海东博士的《潮汐调和分析原理和应用》和调和分析软件S_Tide
    JMeter 随机数生成器简介:使用 Random 和 UUID 算法
    openGauss数据库的安装部署
    SAP MTS/ATO/MTO/ETO专题之九:M+M模式前后台操作,策略用50,提前准备原材料和半成品
    盘点 11 月火火火火的 GitHub 项目
  • 原文地址:https://blog.csdn.net/OneFlow_Official/article/details/125924221