<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          【從零開始學(xué)深度學(xué)習(xí)編譯器】一,深度學(xué)習(xí)編譯器及TVM 介紹

          共 11143字,需瀏覽 23分鐘

           ·

          2021-03-20 12:00

          0x0. 介紹

          大家好呀,在過去的半年到一年時間里,我分享了一些算法解讀,算法優(yōu)化,模型轉(zhuǎn)換相關(guān)的一些文章。這篇文章是自己開啟學(xué)習(xí)深度學(xué)習(xí)編譯器的第一篇文章,后續(xù)也會努力更新這個系列。這篇文章是開篇,所以我不會太深入講解TVM的知識,更多的是介紹一下深度學(xué)習(xí)編譯器和TVM是什么?以及為什么我要選擇學(xué)習(xí)TVM,最后我也會給出一個讓讀者快速體驗TVM效果的一個開發(fā)環(huán)境搭建的簡要教程以及一個簡單例子。

          0x1. 為什么需要深度學(xué)習(xí)編譯器?

          深度學(xué)習(xí)編譯器這個詞語,我們可以先拆成兩個部分來看。

          首先談?wù)勆疃葘W(xué)習(xí)領(lǐng)域。從訓(xùn)練框架角度來看,Google的TensorFlow和FaceBook的Pytorch是全球主流的深度學(xué)習(xí)框架,另外亞馬遜的MxNet,百度的Paddle,曠視的MegEngine,華為的Mindspore以及一流科技的OneFlow也逐漸在被更多人接受和使用。這么多訓(xùn)練框架,我們究竟應(yīng)該選擇哪個?如果追求易用性,可能你會選擇Pytorch,如果追求項目部署落地,可能你會選擇TensorFlow,如果追求分布式訓(xùn)練最快可能你會體驗OneFlow。所以這個選擇題沒有確定答案,在于你自己的喜好。從推理框架角度來看,無論我們選擇何種訓(xùn)練框架訓(xùn)練模型,我們最終都是要將訓(xùn)練好的模型部署到實際場景的,在模型部署的時候我們會發(fā)現(xiàn)我們要部署的設(shè)備可能是五花八門的,例如Intel CPU/Nvidia GPU/Intel GPU/Arm CPU/Arm GPU/FPGA/NPU(華為海思)/BPU(地平線)/MLU(寒武紀(jì)),如果我們要手寫一個用于推理的框架在所有可能部署的設(shè)備上都達(dá)到良好的性能并且易于使用是一件非常困難的事。

          一般要部署模型到一個指定設(shè)備上,我們一般會使用硬件廠商自己推出的一些前向推理框架,例如在Intel的CPU/GPU上就使用OpenVINO,在Arm的CPU/GPU上使用NCNN/MNN等,在Nvidia GPU上使用TensorRT。雖然針對不同的硬件設(shè)備我們使用特定的推理框架進(jìn)行部署是最優(yōu)的,但這也同時存在問題,比如一個開發(fā)者訓(xùn)練了一個模型需要在多個不同類型的設(shè)備上進(jìn)行部署,那么開發(fā)者需要將訓(xùn)練的模型分別轉(zhuǎn)換到特定框架可以讀取的格式,并且還要考慮各個推理框架OP實現(xiàn)是否完全對齊的問題,然后在不同平臺部署時還容易出現(xiàn)的問題是開發(fā)者訓(xùn)練的模型在一個硬件上可以高效推理,部署到另外一個硬件上性能驟降。并且從之前幾篇探索ONNX的文章來看,不同框架間模型轉(zhuǎn)換工作也是阻礙各種訓(xùn)練框架模型快速落地的一大原因。

          接下來,我們要簡單描述一下編譯器。實際上在編譯器發(fā)展的早期也和要將各種深度學(xué)習(xí)訓(xùn)練框架的模型部署到各種硬件面臨的情況一下,歷史上出現(xiàn)了非常多的編程語言,比如C/C++/Java等等,然后每一種硬件對應(yīng)了一門特定的編程語言,再通過特定的編譯器去進(jìn)行編譯產(chǎn)生機(jī)器碼,可以想象隨著硬件和語言的增多,編譯器的維護(hù)難度是多么困難。還好現(xiàn)代的編譯器已經(jīng)解決了這個問題,那么這個問題編譯器具體是怎么解決的呢?

          為了解決上面的問題,科學(xué)家為編譯器抽象出了編譯器前端,編譯器中端,編譯器后端等概念,并引入IR (Intermediate Representation)的概率。解釋如下:

          • 編譯器前端:接收C/C++/Java等不同語言,進(jìn)行代碼生成,吐出IR
          • 編譯器中端:接收IR,進(jìn)行不同編譯器后端可以共享的優(yōu)化,如常量替換,死代碼消除,循環(huán)優(yōu)化等,吐出優(yōu)化后的IR
          • 編譯器后端:接收優(yōu)化后的IR,進(jìn)行不同硬件的平臺相關(guān)優(yōu)化與硬件指令生成,吐出目標(biāo)文件

          以LLVM編譯器為例子,借用藍(lán)色(知乎ID)大佬的圖:

          編譯器抽象

          受到編譯器解決方法的啟發(fā),深度學(xué)習(xí)編譯器被提出,我們可以將各個訓(xùn)練框架訓(xùn)練出來的模型看作各種編程語言,然后將這些模型傳入深度學(xué)習(xí)編譯器之后吐出IR,由于深度學(xué)習(xí)的IR其實就是計算圖,所以可以直接叫作Graph IR。針對這些Graph IR可以做一些計算圖優(yōu)化再吐出IR分發(fā)給各種硬件使用。這樣,深度學(xué)習(xí)編譯器的過程就和傳統(tǒng)的編譯器類似,可以解決上面提到的很多繁瑣的問題。仍然引用藍(lán)色大佬的圖來表示這個思想。

          深度學(xué)習(xí)編譯器抽象

          0x02. TVM

          基于上面深度學(xué)習(xí)編譯器的思想,陳天奇領(lǐng)銜的TVM橫空出世。TVM就是一個基于編譯優(yōu)化的深度學(xué)習(xí)推理框架(暫且說是推理吧,訓(xùn)練功能似乎也開始探索和接入了),我們來看一下TVM的架構(gòu)圖。來自于:https://tvm.apache.org/2017/10/06/nnvm-compiler-announcement

          TVM架構(gòu)圖

          從這個圖中我們可以看到,TVM架構(gòu)的核心部分就是NNVM編譯器(注意一下最新的TVM已經(jīng)將NNVM升級為了Realy,所以后面提到的Relay也可以看作是NNVM)。NNVM編譯器支持直接接收深度學(xué)習(xí)框架的模型,如TensorFlow/Pytorch/Caffe/MxNet等,同時也支持一些模型的中間格式如ONNX、CoreML。這些模型被NNVM直接編譯成Graph IR,然后這些Graph IR被再次優(yōu)化,吐出優(yōu)化后的Graph IR,最后對于不同的后端這些Graph IR都會被編譯為特定后端可以識別的機(jī)器碼完成模型推理。比如對于CPU,NNVM就吐出LLVM可以識別的IR,再通過LLVM編譯器編譯為機(jī)器碼到CPU上執(zhí)行。

          0x03. 環(huán)境配置

          工欲善其事,必先利其器,再繼續(xù)探索TVM之前我們先了解一下TVM的安裝流程。這里參考著官方的安裝文檔提供兩種方法。

          0x03.1 基于Docker的方式

          我們可以直接拉安裝配置好TVM的docker,在docker中使用TVM,這是最快捷最方便的。例如拉取一個編譯了cuda后端支持的TVM鏡像,并啟動容器的示例如下:

          docker pull tvmai/demo-gpu
          nvidia-docker run --rm -it tvmai/demo-gpu bash

          這樣就可以成功進(jìn)入配置好tvm的容器并且使用TVM了。

          0x03.2 本地編譯以Ubuntu為例

          如果有修改TVM源碼或者給TVM貢獻(xiàn)的需求,可以本地編譯TVM,以Ubuntu為例編譯和配置的流程如下:

          git clone --recursive https://github.com/apache/tvm tvm
          cd tvm
          mkdir build
          cp cmake/config.cmake build
          cd build
          cmake ..
          make -j4
          export TVM_HOME=/path/to/tvm
          export PYTHONPATH=$TVM_HOME/python:${PYTHONPATH}

          這樣我們就配置好了TVM,可以進(jìn)行開發(fā)和測試了。

          我的建議是本地開發(fā)和調(diào)試使用后面的方式,工業(yè)部署使用Docker的方式。

          0x04. 樣例展示

          在展示樣例前說一下我的環(huán)境配置,pytorch1.7.0 && TVM 0.8.dev0

          這里以Pytorch模型為例,展示一下TVM是如何將Pytorch模型通過Relay(可以理解為NNVM的升級版,)構(gòu)建TVM中的計算圖并進(jìn)行圖優(yōu)化,最后再通過LLVM編譯到Intel CPU上進(jìn)行執(zhí)行。最后我們還對比了一下基于TVM優(yōu)化后的Relay Graph推理速度和直接使用Pytorch模型進(jìn)行推理的速度。這里是以torchvision中的ResNet18為例子,結(jié)果如下:

          Relay top-1 id: 282class name: tiger cat
          Torch top-1 id: 282class name: tiger cat
          Relay time:  1.1846002000000027 seconds
          Torch time:  2.4181047000000007 seconds

          可以看到在預(yù)測結(jié)果完全一致的情況下,TVM能帶來2倍左右的加速。這里簡單介紹一下代碼的流程。這個代碼可以在這里(https://github.com/BBuf/tvm_learn)找到。

          0x04.1 導(dǎo)入TVM和Pytorch并加載ResNet18模型

          import time
          import tvm
          from tvm import relay

          import numpy as np

          from tvm.contrib.download import download_testdata

          # PyTorch imports
          import torch
          import torchvision

          ######################################################################
          # Load a pretrained PyTorch model
          # -------------------------------
          model_name = "resnet18"
          model = getattr(torchvision.models, model_name)(pretrained=True)
          model = model.eval()

          # We grab the TorchScripted model via tracing
          input_shape = [13224224]
          input_data = torch.randn(input_shape)
          scripted_model = torch.jit.trace(model, input_data).eval()

          需要注意的是Relay在解析Pytorch模型的時候是解析TorchScript格式的模型,所以這里使用torch.jit.trace跑一遍原始的Pytorch模型并導(dǎo)出TorchScript模型。

          0x04.2 載入測試圖片

          加載一張測試圖片,并執(zhí)行一些后處理過程。

          from PIL import Image

          img_url = "https://github.com/dmlc/mxnet.js/blob/main/data/cat.png?raw=true"
          img_path = download_testdata(img_url, "cat.png", module="data")
          img = Image.open(img_path).resize((224224))

          # Preprocess the image and convert to tensor
          from torchvision import transforms

          my_preprocess = transforms.Compose(
              [
                  transforms.Resize(256),
                  transforms.CenterCrop(224),
                  transforms.ToTensor(),
                  transforms.Normalize(mean=[0.4850.4560.406], std=[0.2290.2240.225]),
              ]
          )
          img = my_preprocess(img)
          # 新增Batch維度
          img = np.expand_dims(img, 0)

          0x04.3 Relay導(dǎo)入TorchScript模型并編譯到LLVM后端

          接下來我們將PyTorch的graph導(dǎo)入到Relay成為Relay Graph,這里輸入層的名字可以任意指定。然后將Gpath使用給定的配置編譯到LLVM目標(biāo)硬件上。

          ######################################################################
          # Import the graph to Relay
          # -------------------------
          # Convert PyTorch graph to Relay graph. The input name can be arbitrary.
          input_name = "input0"
          shape_list = [(input_name, img.shape)]
          mod, params = relay.frontend.from_pytorch(scripted_model, shape_list)

          ######################################################################
          # Relay Build
          # -----------
          # Compile the graph to llvm target with given input specification.
          target = "llvm"
          target_host = "llvm"
          ctx = tvm.cpu(0)
          with tvm.transform.PassContext(opt_level=3):
              lib = relay.build(mod, target=target, target_host=target_host, params=params)

          0x04.4 在目標(biāo)硬件上進(jìn)行推理并輸出分類結(jié)果

          這里加了一個計時函數(shù)用來記錄推理的耗時情況。

          ######################################################################
          # Execute the portable graph on TVM
          # ---------------------------------
          # Now we can try deploying the compiled model on target.
          from tvm.contrib import graph_runtime

          tvm_t0 = time.clock()
          for i in range(10):
              dtype = "float32"
              m = graph_runtime.GraphModule(lib["default"](ctx))
              # Set inputs
              m.set_input(input_name, tvm.nd.array(img.astype(dtype)))
              # Execute
              m.run()
              # Get outputs
              tvm_output = m.get_output(0)
          tvm_t1 = time.clock()

          接下來我們在1000類的字典里面查詢一下Top1概率對應(yīng)的類別并輸出,同時也用Pytorch跑一下原始模型看看兩者的結(jié)果是否一致和推理耗時情況。

          #####################################################################
          # Look up synset name
          # -------------------
          # Look up prediction top 1 index in 1000 class synset.
          synset_url = "".join(
              [
                  "https://raw.githubusercontent.com/Cadene/",
                  "pretrained-models.pytorch/master/data/",
                  "imagenet_synsets.txt",
              ]
          )
          synset_name = "imagenet_synsets.txt"
          synset_path = download_testdata(synset_url, synset_name, module="data")
          with open(synset_path) as f:
              synsets = f.readlines()

          synsets = [x.strip() for x in synsets]
          splits = [line.split(" "for line in synsets]
          key_to_classname = {spl[0]: " ".join(spl[1:]) for spl in splits}

          class_url = "".join(
              [
                  "https://raw.githubusercontent.com/Cadene/",
                  "pretrained-models.pytorch/master/data/",
                  "imagenet_classes.txt",
              ]
          )
          class_name = "imagenet_classes.txt"
          class_path = download_testdata(class_url, class_name, module="data")
          with open(class_path) as f:
              class_id_to_key = f.readlines()

          class_id_to_key = [x.strip() for x in class_id_to_key]

          # Get top-1 result for TVM
          top1_tvm = np.argmax(tvm_output.asnumpy()[0])
          tvm_class_key = class_id_to_key[top1_tvm]

          # Convert input to PyTorch variable and get PyTorch result for comparison
          torch_t0 = time.clock()
          for i in range(10):
              with torch.no_grad():
                  torch_img = torch.from_numpy(img)
                  output = model(torch_img)

                  # Get top-1 result for PyTorch
                  top1_torch = np.argmax(output.numpy())
                  torch_class_key = class_id_to_key[top1_torch]
          torch_t1 = time.clock()

          tvm_time = tvm_t1 - tvm_t0
          torch_time = torch_t1 - torch_t0

          print("Relay top-1 id: {}, class name: {}".format(top1_tvm, key_to_classname[tvm_class_key]))
          print("Torch top-1 id: {}, class name: {}".format(top1_torch, key_to_classname[torch_class_key]))
          print('Relay time: ', tvm_time / 10.0'seconds')
          print('Torch time: ', torch_time / 10.0'seconds')

          0x05. 小節(jié)

          這一節(jié)是對TVM的初步介紹,暫時講到這里,后面的文章會繼續(xù)深度了解和介紹深度學(xué)習(xí)編譯器相關(guān)的知識。

          0x06. 參考

          • http://tvm.apache.org/docs/tutorials/frontend/from_pytorch.html#sphx-glr-tutorials-frontend-from-pytorch-py
          • https://zhuanlan.zhihu.com/p/50529704

          歡迎關(guān)注GiantPandaCV, 在這里你將看到獨(dú)家的深度學(xué)習(xí)分享,堅持原創(chuàng),每天分享我們學(xué)習(xí)到的新鮮知識。( ? ?ω?? )?

          有對文章相關(guān)的問題,或者想要加入交流群,歡迎添加BBuf微信:

          二維碼

          為了方便讀者獲取資料以及我們公眾號的作者發(fā)布一些Github工程的更新,我們成立了一個QQ群,二維碼如下,感興趣可以加入。

          公眾號QQ交流群


          瀏覽 81
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  外国女人操逼视频网址 | www.成人高清 | 黄色一级片免费看 | 一级h片 | 91探花国产在线播放 |