如何在TVM上集成Codegen(下)

Bring DNNL to TVM: JSON Codegen/Runtime

现在实现将中继图序列化为JSON表示的DNNL codegen,然后实现DNNL JSON runtime来反序列化和执行该图。请注意,如果尝试实现codegen来生成C兼容的程序,可能需要直接进入下一节。

要使TVM中的DNNL JSON codegen/runtime在本例中工作,请确保DNNL在计算机上可用,并在中使用set(USE_DNNL_CODEGEN ON)构建TVM配置文件制作。

DNNL codegen在src/relay/backend/contrib/dnnl/codegen.cc。 因为在这个文件中的两个表单中都实现了DNNL codegen,所以在跟踪代码时,可以将注意力集中在USE_JSON_RUNTIME宏所涵盖的部分。

首先用TVM注册API(L510)注册codegen。此注册使TVM编译引擎将Compiler=<your codegen>的中继函数分派到relay.ext.<your codegen>。然后实现了DNNL编译器(L490)的入口函数。有关详细信息,请阅读代码片段中嵌入的注释:

runtime::Module DNNLCompiler(const ObjectRef& ref) {

// "ref" should be the paritioned Relay function with kCompiler=dnnl.

CHECK(ref->IsInstance<FunctionNode>());

auto func = Downcast<Function>(ref);

// Get the function name as the symbol to match in runtime.

auto func_name = GetExtSymbol(func);

// Serialize the function to a JSON string (introduce later).

DNNLJSONSerializer serializer(func_name, func);

serializer.serialize();

std::string graph_json = serializer.GetJSON();

// The constant tensor names that have been bound to the module.

// All constant tensors will be serialzied along with the JSON graph

// when export_library is invoked.

auto params = serializer.GetParams();

// The function to create DNNL JSON runtime (introduce later).

const auto* pf = runtime::Registry::Get("runtime.DNNLJSONRuntimeCreate");

CHECK(pf != nullptr) << "Cannot find JSON runtime module to create";

// Create a DNNL runtime module that can run the serialized function.

auto mod = (*pf)(func_name, graph_json, params);

return mod;

}

TVM_REGISTER_GLOBAL("relay.ext.dnnl").set_body_typed(DNNLCompiler);

每个 runtime模块只负责一个中继函数,这意味着您可能在一个single .so文件中有多个DNNL runtime模块。

DNNL JSON序列化

接下来,实现dnnljson序列化器(L429)。

我们从BYOC JSON codegen (src/relay/backend/contrib/codegen_json/codegen_json.h)派生而来。DNNL JSON serializer中的特殊进程尝试序列化对可由DNNL JSON runtime解释的JSON节点的复合函数调用。假设我们有一个与模式匹配的复合函数dnnl.conv2d_relu公司,则BYOC JSON codegen将生成以下JSON节点:

{

op: "kernel",

name: "dnnl.conv2d_relu",

inputs: [[0, 0, 0], [1, 0, 0]],

attrs: {

PartitionedFromPattern: ["nn.conv2d_nn.relu_"],

shape: [1, 32, 14, 14]

}

}

问题是在runtime仍然需要Conv2D属性,比如padding和stripes,但是BYOC JSON序列化程序只附加复合函数的属性,而不附加body算子。另一方面,定制的DNNL JSON序列化程序在复合函数中附加第一个也是唯一一个Conv2D的属性,以生成以下JSON节点:

{

op: "kernel",

name: "dnnl.conv2d_relu",

inputs: [[0, 0, 0], [1, 0, 0]],

attrs: {

shape: [1, 32, 14, 14],

data_layout: ["NCHW"],

kernel_layout: ["OIHW"],

strides: [1, 1],

padding: [1, 1, 1, 1]

}

}

从DNNL JSON序列化程序可以看出,只要JSON runtime能够解释,就可以定制序列化程序以生成JSON格式的任何表单。

DNNL JSON Runtime

实现一个DNNL JSON runtime来解释和执行序列化的JSON图。把它放在src/runtime/contrib/dnnl/dnnl_json_runtime.cc

同样,首先注册两个api来创建 runtime,这样就可以在任何地方使用。这个runtime.DNNLJSONRuntimeCreate序列化后在上一部分中使用,并且runtime.module.loadbinary_dnnl_json可以在加载.so back时使用。

// Create a DNNL JSON runtime to interpret and execute the given JSON graph.
runtime::Module DNNLJSONRuntimeCreate(String symbol_name, String graph_json,
                                      const Array<String>& const_names) {
  auto n = make_object<DNNLJSONRuntime>(symbol_name, graph_json, const_names);
  return runtime::Module(n);
}
TVM_REGISTER_GLOBAL("runtime.DNNLJSONRuntimeCreate")
    .set_body_typed(DNNLJSONRuntimeCreate);
 
TVM_REGISTER_GLOBAL("runtime.module.loadbinary_dnnl_json")
    .set_body_typed(JSONRuntimeBase::LoadFromBinary<DNNLJSONRuntime>);

Now we explain DNNL JSON runtime implementation. The basic class structure is:

class DNNLJSONRuntime : public JSONRuntimeBase {
  const  char* type_key() const { return  "dnnl_json"; } 
  void Init(const Array<NDArray>& consts) override {
    // Initialize the DNNL graph engine.
    BuildEngine();
    
    // Setup constants entries for weights.
    CHECK_EQ(consts.size(), const_idx_.size())
      << "The number of input constants must match the number of required.";
    SetupConstants(consts);
  }
 
  void Run() override {
   // 1. Fill in the input buffers.
   // 2. Invoke the engine through intepreting the stream.
   // 3. Read and fill output buffers.
  }
}

Init函数负责通过解释JSON图形字符串来构建DNNL引擎(BuildEngine请参阅L93),并将常量权重填充到相应的数据输入缓冲区(SetupConstant在JSON runtime基类中实现,只需在Init中调用它)。

即使我们运行多次推断,这个函数也只会被调用一次。

接下来,Run函数(L64)首先将输入张量(可能来自用户输入或恒定权重)写入构建DNNL引擎时初始化的相应DNNL内存缓冲区。然后启动DNNL引擎来执行JSON图。最后,它将DNNL输出内存缓冲区写回相应的输出张量。

由于DNNL JSON runtime中的rest实现太过DNNL特定,因此在本文中我们将停止讨论。要强调的是,虽然DNNL JSON runtime是一个很好的参考,但是JSON runtime可以完全定制以满足需求。

Bring DNNL to TVM: C Source Codegen

现在让我们实现DNNL codegen,它生成C源代码,它调用dnnlapi来执行中继图表。注释如果试图实现一个codegen来生成JSON格式的其他图形表示,那么可能需要阅读DNNL to TVM: JSON Codegen/Runtime并跳过这一节。

要使TVM中的DNNL C源代码生成在本例中工作,确保DNNL在计算机上可用,并在中使用set(USE_DNNL_CODEGEN C_SRC)构建TVM配置文件制作.

DNNL codegen在src/relay/backend/contrib/dnnl/codegen.cc。由于在这个文件中的两个表单中都实现了DNNL codegen,所以在跟踪代码时,可以将注意力集中在USE_JSON_RUNTIME runtime宏未涵盖的部分。

首先用TVM注册API(L510)注册codegen。此注册使TVM编译引擎将Compiler=<your codegen>的中继函数分派到relay.ext.<your codegen>。然后实现DNNL编译器(L490)的entry函数:

runtime::Module DNNLCompiler(const ObjectRef& ref) {

DNNLModuleCodegen dnnl;

return dnnl.CreateCSourceModule(ref);

}

TVM_REGISTER_GLOBAL("relay.ext.dnnl").set_body_typed(DNNLCompiler);

每个 runtime模块只负责一个中继函数,这意味着您可能在single .so文件中有多个DNNL runtime模块。

然后,推导了CSourceModuleCodegenBase,在L362中实现了DNNLModuleCodegen。虽然CSourceModuleCodegenBase负责序列化等其他模块级流程,只需要在CreateCSourceModule函数(L389)中实现DNNL代码生成:

runtime::Module CreateCSourceModule(const ObjectRef& ref) override {

// Include headers

// ...skip...

code_stream_ << "#include <dnnl/dnnl_kernel.h>\n";

// ...skip...

// "ref" should be the paritioned Relay function with kCompiler=dnnl.

CHECK(ref->IsInstance<FunctionNode>());

auto res = GenDNNLFunc(Downcast<Function>(ref));

// "code" is the generated C code with DNNL APIs.

std::string code = code_stream_.str();

// "res" is a tuple of constant weights (symbols, values).

// All constant tensors will be serialzied along with the generated C code

// when export_library is invoked.

String sym = std::get<0>(res);

Array<String> variables = std::get<1>(res);

// Create a CSource module with all above artifacts.

const auto* pf = runtime::Registry::Get("runtime.CSourceModuleCreate");

CHECK(pf != nullptr) << "Cannot find csource module to create the external runtime module";

return (*pf)(code, "c", sym, variables);

}

接下来,实现GenDNNLFunc(L365),用DNN API生成可编译的C代码,如下所示。有关TVM C source runtime模块兼容函数接口的说明,请参阅嵌入的注释。

// The example Relay graph: conv2d -> add -> relu.

#include <cstdint>

#include <cstdlib>

#include <cstring>

#include <vector>

#include <tvm/runtime/c_runtime_api.h>

#include <tvm/runtime/container.h>

#include <tvm/runtime/packed_func.h>

#include <dlpack/dlpack.h>

#include <dnnl/dnnl_kernel.h>

using namespace tvm::runtime;

using namespace tvm::runtime::contrib;

// Execute the conv2d->add->relu graph with DNNL.

extern "C" void dnnl_0_(float* dnnl_0_i0, float* dnnl_0_i1,

float* dnnl_0_i2, float* out0) {

// Allocate intermediate buffers.

float* buf_0 = (float*)std::malloc(4 * 4608);

float* buf_1 = (float*)std::malloc(4 * 4608);

float* buf_2 = (float*)std::malloc(4 * 4608);

// Pre-implemented op-based DNNL functions.

dnnl_conv2d(dnnl_0_i0, dnnl_0_i1, buf_0, 1, 32, 14, 14, 32, 1, 0, 0, 3, 3, 1, 1);

dnnl_add(buf_0, dnnl_0_i2, buf_1, 1, 32, 12, 12);

dnnl_relu(buf_1, buf_2, 1, 32, 12, 12);

// Copy the final output to the corresponding buffer.

std::memcpy(out0, buf_2, 4 * 4608);

std::free(buf_0);

std::free(buf_1);

std::free(buf_2);

}

// The wrapper function with all arguments in DLTensor type.

extern "C" int dnnl_0_wrapper_(DLTensor* arg0,

DLTensor* arg1,

DLTensor* arg2,

DLTensor* out0) {

// Cast all DLTensor to primitive type buffers and invoke the above

// execution function.

dnnl_0_(static_cast<float*>(arg0->data),

static_cast<float*>(arg1->data),

static_cast<float*>(arg2->data),

static_cast<float*>(out0->data));

return 0;

}

// The TVM macro to generate TVM runtime compatible function "dnnl_0"

// from our generated "dnnl_0_wrapper_".

TVM_DLL_EXPORT_TYPED_FUNC(dnnl_0, dnnl_0_wrapper_);

预先实现的基于算子的DNNL函数位于src/runtime/contrib/dnnl/dnnl.cc。

因为rest实现在src/relay/backend/contrib/dnnl/codegen.cc太DNNL的具体细节,本文就到此为止。其主要思想是实现一个中继图访问者(L138)来访问给定的中继函数并生成上面的C代码。只要codegen能够生成与TVM运行时兼容的C代码,就可以完全定制codegen以满足您的需求。

C Source Compilation

DNNLCompiler的输出是一个模块,其中生成的C代码是文本格式的,GCC尚未将其编译为可执行的二进制文件。实际上,当用户调用export_libray(mod)时,会编译生成的C代码,如下面的代码片段:

def update_lib(lib):
    # Include the path of src/runtime/contrib/dnnl/dnnl.cc
    test_dir = os.path.dirname(os.path.realpath(os.path.expanduser(__file__)))
    source_dir = os.path.join(test_dir, "..", "..", "..")
    contrib_path = os.path.join(source_dir, "src", "runtime", "contrib")
 
    # Setup the gcc flag to compile DNNL code.
    kwargs = {}
    kwargs["options"] = ["-O2", "-std=c++14", "-I" + contrib_path]
    tmp_path = util.tempdir()
    lib_name = 'lib.so'
    lib_path = tmp_path.relpath(lib_name)
 
    # The generated C code with DNNL APIs is compiled to a binary lib.so.
    lib.export_library(lib_path, fcompile=False, **kwargs)
 
    # Load the lib.so back to a runtime module.
    lib = runtime.load_module(lib_path)
    return lib
 
with tvm.transform.PassContext(opt_level=3):
    json, lib, param = relay.build(mod, target=target, params=params)
lib = update_lib(lib)
rt_mod = tvm.contrib.graph_runtime.create(json, lib, ctx)

Bring DNNL to TVM: Build TVM with DNNL Codegen/Runtime

最后,创建cmake/modules/contrib/DNNL.cmake在构建TVM时包括DNNL codegen。为了演示,DNNL codegen在同一个cmake文件中有两个实现。只能根据需要专注于其中的一个。

cmake文件就绪后,现在用户可以在其构建中指定set(USE_DNNL_CODEGEN ON)的build/config.cmake配置文件制作启用DNNL codegen。

如何在TVM上集成Codegen(下)的更多相关文章

  1. 如何在TVM上集成Codegen(上)

    如何在TVM上集成Codegen(上) 许多常用的深度学习内核,或者提供DNNL或TensorRT等框架和图形引擎,让用户以某种方式描述他们的模型,从而获得高性能.此外,新兴的深度学习加速器也有自己的 ...

  2. 如何在CPU上优化GEMM(下)

    如何在CPU上优化GEMM(下) Array Packing 另一个重要的技巧是数组打包.这个技巧是对数组的存储维度进行重新排序,将某个维度上的连续访问模式在平滑后转换为顺序模式. 如上图所示,在阻塞 ...

  3. 中小研发团队架构实践之生产环境诊断工具WinDbg 三分钟学会.NET微服务之Polly 使用.Net Core+IView+Vue集成上传图片功能 Fiddler原理~知多少? ABP框架(asp.net core 2.X+Vue)模板项目学习之路(一) C#程序中设置全局代理(Global Proxy) WCF 4.0 使用说明 如何在IIS上发布,并能正常访问

    中小研发团队架构实践之生产环境诊断工具WinDbg 生产环境偶尔会出现一些异常问题,WinDbg或GDB是解决此类问题的利器.调试工具WinDbg如同医生的听诊器,是系统生病时做问题诊断的逆向分析工具 ...

  4. Jenkins持续集成(上)-Windows下安装Jenkins

    环境:Windows 2008 R2.Jenkins2.235.1: 概要 前面写过一篇文章,<自动发布-asp.net自动发布.IIS站点自动发布(集成SLB.配置管理.Jenkins)> ...

  5. 如何在windows7上安装启明星系统。

    启明星系统提供多种安装方式.安装包里自带了setup.exe.每个程序的 install下有在线安装(例如请假应用程序为book,则默认为 http://localhost/book/install ...

  6. 如何在 Linux 上安装应用程序

    如何在 Linux 上安装应用程序 编译自:https://opensource.com/article/18/1/how-install-apps-linux作者: Seth Kenlon原创:LC ...

  7. 如何在GitHub上大显身手?

    推荐一篇良许大佬的文章,如何在github上大显身手.拥有自己的github,且有所贡献,这是一件很有意义的的事情,在面试上也是加分项哦,赶紧搞起来. 转载至http://uee.me/aHAfN 这 ...

  8. 实例演示:如何在Kubernetes上大规模运行CI/CD

    本周四晚上8:30,第二期k3s在线培训如约开播!本期课程将介绍k3s的核心架构,如高可用架构以及containerd.一起来进阶探索k3s吧! 报名及观看链接:http://z-mz.cn/PmwZ ...

  9. TensorRT宏碁自建云(BYOC, BuildYourOwnCloud)上集成

    TensorRT宏碁自建云(BYOC, BuildYourOwnCloud)上集成 这个PR增加了对分区.编译和运行TensorRT BYOC目标的支持. Building 有两个新的cmake标志: ...

随机推荐

  1. Android系统自带的android.util.Base64的实现源码

    由于Android逆向还原的时候,经常需要用到android.util.Base64的代码,因此从Android 4.4.4的 系统里抠出来进行备份,懒得用其他的代码进行修改替换了. /* * Cop ...

  2. Intel汇编程序设计-整数算术指令(下)

    7.5  扩展加法和减法 扩展精度的假发和减法是指任意尺寸大小数字的加法和减法.例如要求你写一个C++程序,把两个1024位的整数相加,解决方案可不是那么简单!但在汇编语言中,ADC(带进位加)指令和 ...

  3. Mac SSH工具-Termius

    全平台,功能强大 SSH连接.SFTP连接.端口转发.多设备同步 官方网站

  4. featuretools的几个高级特性

    摘要:记录工作中用到的featuretools的部分高级特性. 1.防止信息泄露 在调用dfs时,将主表的观测时间列连同id列作为cutoff_time,可以在构造特征时自动将子表中在cutoff_t ...

  5. Python JWT 介绍

    Python JWT 介绍 目录 Python JWT 介绍 1. JWT 介绍 2. JWT 创建 token 2.1 JWT 生成原理 2.2 JWT 校验 token 原理 3. 代码实现 4. ...

  6. TypeScript 中限制对象键名的取值范围

    当我们使用 TypeScript 时,我们想利用它提供的类型系统限制代码的方方面面,对象的键值,也不例外. 譬如我们有个对象存储每个年级的人名,类型大概长这样: type Students = Rec ...

  7. RTTI之typeid运算符

    1 #include <iostream> 2 #include <cstdlib> 3 #include <ctime> 4 #include <typei ...

  8. 简单聊聊内存逃逸 | 剑指offer - golang

    问题 简单讲讲golang的内存逃逸吗? 解析 什么是内存逃逸 在程序中,每个函数块都会有自己的内存区域用来存自己的局部变量(内存占用少).返回地址.返回值之类的数据,这一块内存区域有特定的结构和寻址 ...

  9. Gateway导航

    简介 最近都在弄微服务的东西,现在来记录下收获.我从一知半解到现在能从0搭建使用最大的感触有两点 1.微服务各大组件的版本很多,网上很多博客内容不一定适合你的版本,很多时候苦苦琢磨都是无用功 2.网上 ...

  10. [bug] powerdesigner 设置id 自增 Properties中没有identity

    参考 https://blog.csdn.net/qq_37924509/article/details/105215719