将代码生成器带入TVM
將代碼生成器帶入TVM
為了使數據科學家不必擔心開發新模型時的性能,硬件后端提供程序(例如Intel,NVIDIA,ARM等)可以提供諸如cuBLAS或cuDNN之類的內核庫以及許多常用的深度學習內核,或者提供諸如此類的框架。例如帶有圖形引擎的DNNL或TensorRT,使用戶以某種方式描述其模型以實現高性能。此外,新興的深度學習加速器還具有自己的編譯器,內核庫或運行時runtime框架。
當用戶嘗試在新的內核庫或設備上工作時,必須學習新的編程接口。結果,對統一編程接口的需求變得越來越重要,使所有用戶和硬件后端提供程序都站在同一頁面上。
為了與廣泛使用的深度學習框架共享編程接口,許多硬件設備提供商,嘗試將其設備后端集成到TensorFlow。由于TensorFlow沒有為新的后端提供正式的后端接口,必須破解TensorFlow進行注冊,這需要對許多源文件進行更改,使將來的維護變得困難。
演示了作為硬件后端提供的程序,如何輕松利用自帶代碼生成(BYOC)框架,將硬件設備的內核庫/編譯器/框架集成到TVM。利用BYOC框架的最重要優點,設備的所有相關源文件都是獨立的,設備的代碼源/運行時可插入TVM代碼庫。這意味著1)使用代碼源的TVM代碼庫將在上游兼容,以及2)TVM用戶可以根據需要選擇啟用代碼源/運行時。
的其余部分,首先說明可能需要帶有BYOC的TVM的情況,然后概述BYOC編譯和運行時流程。然后,分步說明如何使用英特爾DNNL(又名MKL-DNN,OneDNN)作為運行示例,將供應商庫或執行引擎與BYOC集成到TVM。
將ASIC加速器帶入TVM
首先,讓做一個場景,說明為什么要將加速器引入TVM,以及BYOC框架可以包括哪些功能。
剛剛構建了一個具有ARM CPU和出色的加速器的邊緣設備平臺,該平臺為常見的圖像分類模型提供了出色的性能。加速器在Conv2D,ReLU,GEMM和其它廣泛使用的CNN算子上表現良好。
不幸的是,對象檢測模型也越來越受歡迎,并且客戶需要在平臺上同時運行圖像分類和對象檢測模型。盡管加速器能夠執行對象檢測模型中的幾乎所有算子,但缺少一個算子(例如,非最大抑制,NMS)。
讓TVM執行不受支持的算子
由于TVM具有用于不同后端的多個代碼源,開源社區很容易在短時間內在CPU或GPU上實現新的算子。理想情況下,如果將加速器的編譯流程與BYOC集成到TVM,則TVM將執行中繼圖分區,以將部分圖卸載到加速器,同時將其它圖保持在TVM上。表明平臺能夠運行所有模型,而不必擔心新的運營商。
自定義圖形級優化
ASIC加速器必須具有自己的編譯流程。可能是以下情況之一:
生成圖形表示并將其提供給圖形引擎:可能擁有自己的圖形引擎,該引擎能夠在加速器上執行圖形(或神經網絡模型)。例如,英特爾DNNL和NVIDIA TensorRT都使用引擎來運行整個圖形或模型,能夠1)減少算子之間的內存事務,以及2)通過算子融合優化圖形執行。
為了實現以上兩個優化,可能需要在編譯期間處理圖形。例如,Conv2D和偏差加法是TVM中的兩個單獨的算子,可能是加速器上的一個算子(具有偏差加法功能的Conv2D)。在這種情況下,可能需要通過將conv2d - add圖形模式替換為your_conv2d_with_bias節點來優化圖形。
如果編譯流程屬于這種情況,建議閱讀中的所有其余部分,但跳過將DNNL帶到TVM:C源代碼生成。
生成匯編代碼并將其編譯為可執行的二進制文件:如果沒有像前面那樣的平臺的端到端執行框架,則可能有編譯器以ISA的匯編代碼編譯程序。為了將匯編代碼提供給編譯器,將需要一個代碼生成器,從Relay圖生成和優化匯編代碼。
如果編譯流程屬于這種情況,建議閱讀中的所有其余部分,但跳過將DNNL引入TVM:JSON Codegen / Runtime。
BYOC的工作方式
簡要解釋BYOC框架是如何工作的。有關底層框架組件及其實現的更多詳細說明,請參考開發者文檔。總之,給定圖1中的中繼圖,BYOC框架執行以下步驟:
圖1:原始中繼圖。
1.圖注解
制作用戶提供的中繼圖,第一步是在圖中注釋可能卸載到加速器的節點。將需要遵循“將DNNL引入TVM:注釋規則”以實現受支持的算子的白名單,或定制組合算子的圖形模式列表。示例注釋結果如圖2所示。
圖2:帶注解的圖。
2.圖變換
第二步是基于注釋對圖形進行轉換和優化。具體來說,BYOC執行以下轉換。
2.1:合并編譯器區域:如圖2所示,圖中現在有許多“區域”可以卸載到加速器中,實際上其中一些區域可以合并,以減少數據傳輸和內核啟動開銷。因此,步驟2.1使用貪婪算法來合并盡可能多的那些區域,同時保證功能正確性。結果如圖3所示。
圖3:合并編譯器區域后。
2.2:分區圖:對于上一步中的每個區域,創建一個帶有屬性的Relay函數,Compiler以指示該Relay函數應該完全卸載到加速器上,如圖4所示。
圖4:圖分區之后。
3.代碼生成
現在知道應該卸載中繼圖的哪一部分了。在此步驟中,將每個中繼功能依次發送Compiler=your_accelerator到代碼源。代碼生成器應將Relay函數編譯為,與自己的編譯流程相匹配的形式。可以是C源代碼或任何文本格式。
最后,所有已編譯的函數將與其它未卸載的Relay函數一起.so由TVM export_libraryPython API序列化為單個文件。換句話說,.so運行此流程后,用戶將僅獲得一個文件。
4.運行時runtime
可能還需要實現運行時,初始化圖形引擎(如果適用)并執行已編譯的函數。在推理期間,當TVM運行時遇到圖4中的相應函數調用時,TVM運行時(即圖形運行時或VM)將利用運行時來調用已卸載的函數。運行時負責使用給定的輸入張量,啟動編譯后的函數。將數組結果填充到輸出張量數組中。
以DNNL為例,演示如何使用BYOC框架實現上述工作流程。所有引用的代碼和行號均基于TVM存儲庫的master分支commit 8a0249c。
將DNNL帶到TVM:注釋規則
BYOC框架為提供了兩種描述受支持的算子和模式的方法。可以同時使用。以DNNL為例來說明如何使用。將代碼源的注釋規則放在下python/tvm/relay/op/contrib/your_codegen_name.py。
單一運營商規則
可使用BYOC API直觀地指定加速器支持哪些中繼算子。例如,使用以下代碼段,構建一條規則,該規則表明DNNL代碼源支持Conv2D:
@tvm.ir.register_op_attr(“nn.conv2d”, “target.dnnl”)
def _dnnl_conv2d_wrapper(attrs, args):
return True
target.dnnl將向中繼nn.conv2d算子注冊一個新屬性。通過這種方式,BYOC注釋可以target.dnnl()為圖中的每個算子調用,檢查DNNL代碼源中是否支持。
另一方面,為每個操作員編寫上面的代碼段可能很繁瑣。對于DNNL實施,實現了一個輔助函數_register_external_op_helper,使用更方便:
def _register_external_op_helper(op_name, supported=True):
@tvm.ir.register_op_attr(op_name, “target.dnnl”)
def _func_wrapper(attrs, args):
return supported
return _func_wrapper
_register_external_op_helper(“nn.batch_norm”)
_register_external_op_helper(“nn.conv2d”)
_register_external_op_helper(“nn.dense”)
_register_external_op_helper(“nn.relu”)
_register_external_op_helper(“add”)
_register_external_op_helper(“subtract”)
_register_external_op_helper(“multiply”)
在上面的示例中,指定了DNNL代碼源可以支持的算子列表。
圖形模式規則
加速器或編譯器可能已將某些模式(例如Conv2D + add + ReLU)優化為單個指令或API。在這種情況下,可以指定從圖形模式到指令/ API的映射。對于DNNL,Conv2D API已經包含了偏差加法,允許附加下一個ReLU,將DNNL稱為以下代碼片段:
DNNLConv2d(const bool has_bias = false, const bool has_relu = false) {
// … skip …
auto conv_desc = dnnl::convolution_forward::desc(
dnnl::prop_kind::forward_inference,
dnnl::algorithm::convolution_direct,
conv_src_md, conv_weights_md, conv_bias_md, conv_dst_md,
strides_dims, padding_dims_l, padding_dims_r);
// Attach ReLU
dnnl::primitive_attr attr;
if (has_relu) {
dnnl::post_ops ops;
ops.append_eltwise(1.f, dnnl::algorithm::eltwise_relu, 0.f, 0.f);
attr.set_post_ops(ops);
}
auto conv2d_prim_desc = dnnl::convolution_forward::primitive_desc(
conv_desc, attr, engine_);
// … skip …
在這種情況下,除了用于單個conv2d,映射圖模式conv2d+relu到DNNLConv2d(false, true),映射conv2d+add+relu到DNNLConv2d(true, true)。可使用以下代碼片段實現目的:
def make_pattern(with_bias=True):
data = wildcard()
weight = wildcard()
bias = wildcard()
conv = is_op(‘nn.conv2d’)(data, weight)
if with_bias:
conv_out = is_op(‘add’)(conv, bias)
else:
conv_out = conv
return is_op(‘nn.relu’)(conv_out)
@register_pattern_table(“dnnl”)
def pattern_table():
conv2d_bias_relu_pat = (“dnnl.conv2d_bias_relu”, make_pattern(with_bias=True))
conv2d_relu_pat = (“dnnl.conv2d_relu”, make_pattern(with_bias=False))
dnnl_patterns = [conv2d_bias_relu_pat, conv2d_relu_pat]
return dnnl_patterns
在DNNL示例中,實現了兩個具有不同名稱的模式,以便可以在代碼源中輕松識別。這些模式以中繼模式語言實現。
使用模式表,使用Relay傳遞來執行
%1 = nn.conv2d(%data, %weight, …)
%2 = add(%1, %bias)
%3 = nn.relu(%2)
到
%1 = fn(%input1, %input2, %input3,
Composite=“dnnl.conv2d_bias_relu”,
PartitionedFromPattern=“nn.conv2d_add_nn.relu_”) {
%1 = nn.conv2d(%input1, %input2, …)
%2 = add(%1, %input3)
nn.relu(%2)
}
%2 = %1(%data, %weight, %bias)
DNNL代碼生成器,獲取模式名稱conv2d_bias_relu,映射%1到DNNLConv2d(true, true)。
可能已經注意到,復合函數中還有一個名為“ PartitionedFromPattern”的屬性。如果模式包含wildcard算子,這可能會有所幫助。例如,可能有一個模式表(“conv2d_with_something”, conv2d -> *):
def make_pattern(with_bias=True):
data = wildcard()
weight = wildcard()
conv = is_op(‘nn.conv2d’)(data, weight)
return wildcard()(conv)
在這種情況下,將獲得帶有的復合函數Composite=conv2d_with_something,不知道實際匹配的圖形。那就是PartitionedFromPattern起作用的地方。通過查看匹配圖是否為conv2d -> add或conv2d -> relu,可以知道是否PartitionedFromPattern為nn.conv2d_add_或nn.conv2d_nn.relu_。
將DNNL引入TVM:中繼圖轉換
利用上一步中的注釋規則,現在可以應用BYOC中繼傳遞列表,將中繼圖從圖1轉換為圖4:
mod = create_relay_module_from_model() # Output: Figure 1
mod = transform.MergeComposite(pattern_table)(mod)
mod = transform.AnnotateTarget([“dnnl”])(mod) # Output: Figure 2
mod = transform.MergeCompilerRegions()(mod) # Output: Figure 3
mod = transform.PartitionGraph()(mod) # Output: Figure 4
每個中繼傳遞都可以映射到在BYOC工作原理中引入的步驟。
將DNNL引入TVM:JSON代碼生成/運行時
讓實現將中繼圖序列化為JSON表示的DNNL代碼源,然后實現DNNL JSON運行時以反序列化并執行該圖。如果嘗試實現一個代碼生成器來生成C兼容程序,需要直接進入下一部分。
為了使DNNL JSON的代碼生成/運行在TVM就這個例子中工作,確保DNNL可以在機器上,建立TVMset(USE_DNNL_CODEGEN ON)中config.cmake。
DNNL代碼生成是在中實現的src/relay/backend/contrib/dnnl/codegen.cc。以兩種形式實現了DNNLUSE_JSON_RUNTIME代碼生成,在跟蹤代碼時,可以專注于宏所覆蓋的部分。
首先使用TVM注冊API(L510)注冊代碼源。該注冊使TVM編譯引擎以Compiler= 向調度Relay功能relay.ext.。然后,實現DNNL編譯器(L490)的入口函數。閱讀代碼段中嵌入的注釋以獲取詳細信息:
runtime::Module DNNLCompiler(const ObjectRef& ref) {
// “ref” should be the paritioned Relay function with kCompiler=dnnl.
CHECK(ref->IsInstance());
auto func = Downcast(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);
每個運行時模塊僅負責一個中繼功能,這意味著可能在單個.so文件中包含多個DNNL運行時模塊。
DNNL JSON序列化
接下來,實現DNNL JSON序列化器(L429)。從BYOC JSON代碼生成器(src / relay / backend / contrib / codegen_json / codegen_json.h)派生。DNNL JSON序列化程序中的特殊過程,將組合函數調用序列化為DNNL JSON運行時可以解釋的JSON節點。假設有一個與pattern匹配的復合函數dnnl.conv2d_relu, BYOC JSON代碼生成器將生成以下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]
}
}
問題在于,在運行時仍然需要Conv2D屬性,例如padding和stride,但是BYOC JSON序列化器僅附加復合函數的屬性,而不附加主體算子。另一方面,定制的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中的任何形式,只要JSON運行時可以解釋即可。
DNNL JSON運行時
然后,實現DNNL JSON運行時以解釋和執行序列化的JSON圖。放在下面src/runtime/contrib/dnnl/dnnl_json_runtime.cc。
同樣,首先注冊兩個API來創建運行時,以便可以在任何地方使用。在runtime.DNNLJSONRuntimeCreate被序列化后的上一部分中使用,并且runtime.module.loadbinary_dnnl_json裝載時也可使用.so了。
// Create a DNNL JSON runtime to interpret and execute the given JSON graph.
runtime::Module DNNLJSONRuntimeCreate(String symbol_name, String graph_json,
const Array& const_names) {
auto n = make_object(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);
現在,解釋DNNL JSON運行時實現。基本的類結構為:
class DNNLJSONRuntime : public JSONRuntimeBase {
const char* type_key() const { return “dnnl_json”; }
void Init(const Array& 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引擎(見L93的BuildEngine),并填補了固定的權重,以相應的數據輸入緩沖區(SetupConstant在JSON運行基類來實現,所以需要調用它在Init)。即使運行了多次推斷,該函數也只會被調用一次。
接下來,Run函數(L64)首先將輸入張量(可能來自用戶輸入或恒定權重)寫入在構建DNNL引擎時初始化的相應DNNL存儲緩沖區。然后啟動DNNL引擎以執行JSON圖。最后,將DNNL輸出存儲緩沖區寫回到相應的輸出張量。
由于DNNL JSON運行時中的其余實現都是DNNL特有的,不做詳細介紹。盡管DNNL JSON運行時是一個很好的開始,但JSON運行時可以完全自定義以滿足要求。
將DNNL帶到TVM:C源代碼生成
實現DNNL代碼生成器,該代碼生成器生成C源代碼,該源代碼調用DNNL API來執行中繼圖。如果嘗試實現一個代碼生成器,生成其它圖形表示形式(如JSON格式)。
為了能夠在TVM CODEGEN對這個例子的工作DNNL C源代碼,確保DNNL可以在機器上,建立TVMset(USE_DNNL_CODEGEN C_SRC)中config.cmake。
DNNL代碼生成是在中實現的src/relay/backend/contrib/dnnl/codegen.cc。由于在這個文件用于說明目的實現的代碼生成DNNL兩種形式,可以專注于部分不被覆蓋USE_JSON_RUNTIME宏跟蹤代碼時。
首先使用TVM注冊API(L510)注冊代碼源。該注冊使TVM編譯引擎以Compiler= 向調度Relay功能relay.ext.。實現DNNL編譯器的入口函數(L490):
runtime::Module DNNLCompiler(const ObjectRef& ref) {
DNNLModuleCodegen dnnl;
return dnnl.CreateCSourceModule(ref);
}
TVM_REGISTER_GLOBAL(“relay.ext.dnnl”).set_body_typed(DNNLCompiler);
每個運行時模塊僅負責一個中繼功能,這意味著可能在單個.so文件中包含多個DNNL運行時模塊。
然后,在L362中派生CSourceModuleCodegenBase實施。而負責其它模塊級過程,如序列化的,只需要實現DNNL代碼生成函數(L389):DNNLModuleCodegenCSourceModuleCodegenBaseCreateCSourceModule
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)來使用DNNL API生成可編譯的C代碼,如下所示。參閱嵌入的注釋,以獲取與TVM C源運行時模塊兼容的功能接口的說明。
// The example Relay graph: conv2d -> add -> relu.
#include
#include
#include
#include
#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_);
預先實現的基于op的DNNL函數位于src / runtime / contrib / dnnl / dnnl.cc中。
其余實現src/relay/backend/contrib/dnnl/codegen.cc都過于DNNL,無法進行詳細介紹。主要思想是實現一個中繼圖訪問者(L138)以訪問給定的Relay函數,生成上面的C代碼。只要代碼生成器能夠生成與TVM運行時兼容的C代碼,就可以完全自定義代碼生成器以符合要求。
C源代碼編譯
輸出的DNNLCompiler是一個帶有生成的C代碼的文本格式的模塊,該模塊尚未被編譯gcc為可執行二進制文件。實際上,生成的C代碼將在用戶調用時進行編譯export_libray(mod),如以下代碼片段所示:
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)
將DNNL引入TVM:使用DNNL Codegen / Runtime構建TVM
最后,在構建TVM時創建cmake / modules / contrib / DNNL.cmake,包含DNNL代碼源。DNNL代碼生成器在同一cmake文件中具有兩個實現,根據需要專注于其中之一。
在準備好cmake文件之后,用戶可以set(USE_DNNL_CODEGEN ON)在其中指定build/config.cmake啟用DNNL代碼生成。
總結
以上是生活随笔為你收集整理的将代码生成器带入TVM的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: CUDA上的量化深度学习模型的自动化优化
- 下一篇: TVM自动调度器