<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="https://lmzheng.net/feed.xml" rel="self" type="application/atom+xml" /><link href="https://lmzheng.net/" rel="alternate" type="text/html" /><updated>2026-07-03T20:23:54-07:00</updated><id>https://lmzheng.net/feed.xml</id><title type="html">Lianmin Zheng</title><subtitle>Ph.D. Student at UC Berkeley</subtitle><author><name>Lianmin Zheng</name><email>lianminzheng@gmail.com</email></author><entry><title type="html">Automatic Kernel Optimization for Deep Learning on All Hardware Platforms</title><link href="https://lmzheng.net/posts/2018/10/auto-tune-all" rel="alternate" type="text/html" title="Automatic Kernel Optimization for Deep Learning on All Hardware Platforms" /><published>2018-10-02T00:00:00-07:00</published><updated>2018-10-02T00:00:00-07:00</updated><id>https://lmzheng.net/posts/2018/10/auto-tune-all</id><content type="html" xml:base="https://lmzheng.net/posts/2018/10/auto-tune-all"><![CDATA[<p>Optimizing the performance of deep neural network on a diverse range of hardware platforms is still a hard
problem for AI developers. In terms of system support, we are facing a many-to-many problem here:
deploying trained models from multiple frontends (e.g. Tensorflow, ONNX, MXNet) to multiple
hardware platforms (e.g. CPU, GPU, Accelerators). The most performance critical part of
this problem is obtaining high performance kernel implementations for growing model
architectures and hardware platforms.</p>

<p>To address this challenge, TVM takes a full stack compiler approach.
TVM combines code generation and auto-tuning to generate kernels that are comparable to heavily hand-optimized libraries,
obtaining state-of-the-art inference performance on hardware platforms including
ARM CPUs, Intel CPUs, Mali GPUs, NVIIDA GPUs and AMD GPUs.</p>

<p>In this blog post, we show the workflow of automatic kernel optimization in TVM compiler stack and 
benchmark results on several hardware platforms.</p>

<h1 id="system-overview">System Overview</h1>

<p style="text-align: center"><img src="/images/autotune-all/overview.png" alt="image" width="50%" /></p>
<center> Figure 1. System Overview </center>
<p></p>

<p>Kernel optimization in TVM is done in an iterative loop fashion.
As shown in Figure 1, the automatic kernel optimization takes a neural network (typically in computational graph representation)
from frontend frameworks as input, and generates kernels for all operators in this network.</p>

<p>The inner loop uses a scalable RPC runtime, machine learning based tuners and a tensor compiler.
In each round of the loop, the tuner picks a batch of promising candidate kernel implementations from a large search space,
and profile them on real hardware. Then the tuner gets the profiling results. These profiling results are used as training 
data to fit a prediction model. After fitting the prediction model, the tuner picks the next promising candidates according to the predictions,
and the loop continues. This way, we search for fast kernels iteratively.</p>

<p>The below figure compares traditional auto-tuning and AutoTVM. 
The major difference is that AutoTVM is</p>
<ul>
  <li><strong>Scalable</strong> to heterogenous cluster of devices</li>
  <li><strong>Learning</strong> to optimize tensor programs with a transferable machine learning cost model</li>
</ul>

<p>You can refer to our paper[1] for more details.</p>

<p style="text-align: center"><img src="/images/autotune-all/autotvm.png" alt="image" width="70%" /></p>
<center> Figure 2. Compare Traditional Auto-tuning and AutoTVM </center>
<p></p>

<h2 id="begin-tuning">Begin Tuning</h2>
<p>For demonstration, we run our optimization for resnet-18 on RK3399, an ARM development board.
The detailed instructions are omitted due to the space limit of a blog post.
Links to tutorials for ARM CPU, Mali GPU, NVIDIA GPU, AMD GPU are all available at the end of this blog.</p>

<p>First we get a pre-trained model from MXNet model zoo, and extract tuning tasks from it.</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">from</span> <span class="nn">mxnet.gluon.model_zoo.vision</span> <span class="kn">import</span> <span class="n">get_model</span>

<span class="n">block</span> <span class="o">=</span> <span class="n">get_model</span><span class="p">(</span><span class="s">'resnet18_v1'</span><span class="p">,</span> <span class="n">pretrained</span><span class="o">=</span><span class="bp">True</span><span class="p">)</span>
<span class="n">net</span><span class="p">,</span> <span class="n">params</span> <span class="o">=</span> <span class="n">nnvm</span><span class="p">.</span><span class="n">frontend</span><span class="p">.</span><span class="n">from_mxnet</span><span class="p">(</span><span class="n">block</span><span class="p">)</span>

<span class="n">tasks</span> <span class="o">=</span> <span class="n">autotvm</span><span class="p">.</span><span class="n">extract_from_graph</span><span class="p">(</span><span class="n">net</span><span class="p">)</span>
<span class="n">tune_tasks</span><span class="p">(</span><span class="n">tasks</span><span class="p">,</span> <span class="o">**</span><span class="n">tuning_option</span><span class="p">)</span>
</code></pre></div></div>
<p>There are 12 different conv2d layers in resnet-18, so we launch 12 tuning tasks.
For each of them, the tuner makes several hundreds of trials and picks the best one.
After finishing all tuning tasks, we compile the whole network and generate a single deployable minimal library.
One sample output is</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Extract tasks...
Tuning...
[Task  1/12]  Current/Best:   22.37/  52.19 GFLOPS | Progress: (544/1000) | 406.59 s Done.
[Task  2/12]  Current/Best:    6.51/  18.77 GFLOPS | Progress: (608/1000) | 325.05 s Done.
[Task  3/12]  Current/Best:    4.67/  24.87 GFLOPS | Progress: (480/1000) | 372.31 s Done.
[Task  4/12]  Current/Best:   11.35/  46.83 GFLOPS | Progress: (736/1000) | 602.39 s Done.
[Task  5/12]  Current/Best:    1.01/  19.80 GFLOPS | Progress: (448/1000) | 262.16 s Done.
[Task  6/12]  Current/Best:    2.47/  23.76 GFLOPS | Progress: (672/1000) | 563.85 s Done.
[Task  7/12]  Current/Best:   14.57/  33.97 GFLOPS | Progress: (544/1000) | 465.15 s Done.
[Task  8/12]  Current/Best:    1.13/  17.65 GFLOPS | Progress: (576/1000) | 365.08 s Done.
[Task  9/12]  Current/Best:   14.45/  22.66 GFLOPS | Progress: (928/1000) | 724.25 s Done.
[Task 10/12]  Current/Best:    3.22/  15.36 GFLOPS | Progress: (864/1000) | 564.27 s Done.
[Task 11/12]  Current/Best:   11.03/  32.23 GFLOPS | Progress: (736/1000) | 635.15 s Done.
[Task 12/12]  Current/Best:    8.00/  21.65 GFLOPS | Progress: (1000/1000) | 1111.81 s Done.
Compile...
Upload...
Evaluate inference time cost...
Mean inference time (std dev): 162.59 ms (0.06 ms)
</code></pre></div></div>

<p>The tuning is especially helpful and worth a try if your model has some strange shapes or
your hardware is customized, as hand-optimized static libraries cannot consider all situations.</p>

<h1 id="benchmark-results">Benchmark Results</h1>
<p>We pre-tuned some popular networks on our device cluster and released the following benchmark.
Instructions for reproduction are at the end of this blog.</p>

<p>Comprehensively benchmarking TVM is easy since we have a unified runtime interface.
However maintaining complete, up-to-date, and correct comparisons against all other platforms is not feasible
without expert assistance from the developers of many other projects.
So we put all our numbers in a table, and then provide an incomplete comparison with some other libraries.</p>

<h2 id="comparison">Comparison</h2>
<p>We validate the effectiveness of our automatic optimization stack by 
comparing with heavily optimized traditional libraries on each platform.</p>

<p>We tested popular image classification networks on ImageNet (3x224x224) dataset with batch size = 1 and data type = float32.
The reported numbers are time costs per image in milliseconds.</p>

<h3 id="arm-cpu">ARM CPU</h3>

<p>We choose <a href="https://github.com/Tencent/ncnn">NCNN</a>, a widely used, hand-optimized kernel library as baseline.
It makes extensive use of NEON assembly instructions. For example, the code base contains
<a href="https://github.com/Tencent/ncnn/blob/master/src/layer/arm/convolution_3x3.h">13k lines of code</a> for only 3x3 convolution layers.
We reference the benchmark numbers in their project repository.
As shown in the figure below, TVM outperforms it for all networks on Rasbperry Pi 3B.</p>

<p><img src="/images/autotune-all/arm.png" alt="image" width="90%" /></p>

<h3 id="mali-gpu">Mali GPU</h3>

<p><a href="https://github.com/ARM-software/ComputeLibrary">ARM Compute Library</a> is a vendor provided library that supports Mali GPU (OpenCL) well, so it is selected as baseline.
According to the results, TVM outperforms ARMComputeLib on most networks for single precision (fp32) and achieves the best performance on this board by using half precision (fp16).
TVM shows better scalibility when shifting from fp32 to fp16, while ARMComuteLib fails to optimize for fp16 (using fp16 is even slower in some cases).</p>

<p><img src="/images/autotune-all/mali.png" alt="image" width="90%" /></p>

<h3 id="nvidia-gpu">NVIDIA GPU</h3>

<p>On NVIDIA GPU, <a href="https://developer.nvidia.com/cudnn">CuDNN</a> and <a href="https://developer.nvidia.com/tensorrt">TensorRT</a> are two vendor-provided libraries for training and inference respectively. Since we focus on inference,
we run our benchmark in the unbatched setting. Another tensor compiler <a href="https://github.com/plaidml/plaidml">PlaidML</a> is also reported as baseline
as there is a previous benchmark of it compared against a pre-AutoTVM version of TVM.
We reference its benchmark results from <a href="https://github.com/plaidml/plaidbench">PlaidBench</a>.
According to the results below, TVM achieves parity with TensorRT performance.</p>

<p><img src="/images/autotune-all/nvidia.png" alt="image" width="90%" /></p>

<h3 id="amd-gpu">AMD GPU</h3>

<p>We also take a quick look at a AMD GPU. TVM supports OpenCL and <a href="https://rocm.github.io/">ROCm</a> backend. We found ROCm is better since
it is more specialized for AMD GPUs.
<a href="https://github.com/ROCmSoftwarePlatform/MIOpen">MIOpen</a> is a vendor provided
kernel library. TVM’s graph runtime can call MIOpen’s kernel implementations directly, so we report
the baseline performance by using this integration.</p>

<p>We didn’t do any specific optimization for AMD GPU. All computation definition and schedule code for NVIDIA GPU is directly reused.
As a result, TVM is a little bit slower then MIOpen in most cases.
We believe there is still room for improvement.</p>

<p><img src="/images/autotune-all/amd.png" alt="image" width="90%" /></p>

<h2 id="all-our-results">All Our Results</h2>
<p>We tested the following networks on ImageNet (3x224x224) dataset with batch size = 1 and data type = float32.
The reported numbers are time costs per image in millisecond.</p>

<table>
  <thead>
    <tr>
      <th> </th>
      <th>densenet121</th>
      <th>inception v3</th>
      <th>mobilenet</th>
      <th>mobilenet v2</th>
      <th>resnet18</th>
      <th>resnet50</th>
      <th>squeezenet v1.0</th>
      <th>squeezenet v1.1</th>
      <th>vgg16</th>
      <th>vgg19</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><strong>ARM CPU</strong></td>
      <td> </td>
      <td> </td>
      <td> </td>
      <td> </td>
      <td> </td>
      <td> </td>
      <td> </td>
      <td> </td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>Huawei P20 Pro</td>
      <td>181.4</td>
      <td>439.9</td>
      <td>41.1</td>
      <td>34.5</td>
      <td>76.5</td>
      <td>208.2</td>
      <td>51.8</td>
      <td>25.7</td>
      <td>480.6</td>
      <td>627.0</td>
    </tr>
    <tr>
      <td>Google Pixel2</td>
      <td>162.2</td>
      <td>433.5</td>
      <td>39.5</td>
      <td>30.1</td>
      <td>61.1</td>
      <td>181.3</td>
      <td>47.3</td>
      <td>23.2</td>
      <td>391.1</td>
      <td>487.7</td>
    </tr>
    <tr>
      <td>Firefly RK3399</td>
      <td>335.9</td>
      <td>1285.9</td>
      <td>78.6</td>
      <td>66.7</td>
      <td>161.2</td>
      <td>403.8</td>
      <td>94.6</td>
      <td>48.5</td>
      <td>902.9</td>
      <td>1090.1</td>
    </tr>
    <tr>
      <td>Raspberry Pi 3B</td>
      <td>609.5</td>
      <td>2070.4</td>
      <td>122.2</td>
      <td>103.7</td>
      <td>322.5</td>
      <td>725.8</td>
      <td>185.1</td>
      <td>94.1</td>
      <td>1759.6</td>
      <td>2118.6</td>
    </tr>
    <tr>
      <td>Xilinx PYNQ</td>
      <td>2888.3</td>
      <td>9709.1</td>
      <td>723.5</td>
      <td>514.3</td>
      <td>1234.6</td>
      <td>3580.5</td>
      <td>909.9</td>
      <td>477.3</td>
      <td><sup>-(Note 1)</sup></td>
      <td>-</td>
    </tr>
    <tr>
      <td><strong>Mali GPU</strong></td>
      <td> </td>
      <td> </td>
      <td> </td>
      <td> </td>
      <td> </td>
      <td> </td>
      <td> </td>
      <td> </td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>Mali-T860 MP4</td>
      <td>410.9</td>
      <td>783.1</td>
      <td>75.4</td>
      <td>70.8</td>
      <td>128.6</td>
      <td>352.9</td>
      <td>106.2</td>
      <td>58.0</td>
      <td>679.5</td>
      <td>805.3</td>
    </tr>
    <tr>
      <td>Mali-T860 MP4 (fp16)</td>
      <td>410.9</td>
      <td>783.1</td>
      <td>75.4</td>
      <td>70.8</td>
      <td>128.6</td>
      <td>352.9</td>
      <td>106.2</td>
      <td>58.0</td>
      <td>679.5</td>
      <td>805.3</td>
    </tr>
    <tr>
      <td><strong>NVIDIA GPU</strong></td>
      <td> </td>
      <td> </td>
      <td> </td>
      <td> </td>
      <td> </td>
      <td> </td>
      <td> </td>
      <td> </td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>GTX 1080 Ti</td>
      <td>3.6</td>
      <td>5.8</td>
      <td>0.6</td>
      <td>- <sup>(Note 2) </sup></td>
      <td>-</td>
      <td>2.7</td>
      <td>-</td>
      <td>-</td>
      <td>4.0</td>
      <td>4.6</td>
    </tr>
    <tr>
      <td>GTX TITAN X</td>
      <td>5.8</td>
      <td>9.7</td>
      <td>1.0</td>
      <td>-</td>
      <td>-</td>
      <td>4.3</td>
      <td>-</td>
      <td>-</td>
      <td>6.4</td>
      <td>7.5</td>
    </tr>
    <tr>
      <td>Tegra X2</td>
      <td>26.4</td>
      <td>45.4</td>
      <td>5.1</td>
      <td>-</td>
      <td>-</td>
      <td>25.8</td>
      <td>-</td>
      <td>-</td>
      <td>57.2</td>
      <td>67.6</td>
    </tr>
    <tr>
      <td><strong>AMD GPU</strong></td>
      <td> </td>
      <td> </td>
      <td> </td>
      <td> </td>
      <td> </td>
      <td> </td>
      <td> </td>
      <td> </td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>AMD Vega FE</td>
      <td>5.7</td>
      <td>8.8</td>
      <td>1.0</td>
      <td>-</td>
      <td>-</td>
      <td>4.5</td>
      <td>-</td>
      <td>-</td>
      <td>5.9</td>
      <td>7.0</td>
    </tr>
    <tr>
      <td> </td>
      <td> </td>
      <td> </td>
      <td> </td>
      <td> </td>
      <td> </td>
      <td> </td>
      <td> </td>
      <td> </td>
      <td> </td>
      <td> </td>
    </tr>
  </tbody>
</table>

<ul>
  <li>Note 1: Out of memory on this board.</li>
  <li>Note 2: We didn’t tune some small networks on GPU due to time constraints.
When profiling data is not available, TVM can use fallback code generation. 
But competitive performance is not guaranteed in this scenario.</li>
</ul>

<h1 id="conclusion">Conclusion</h1>
<p>With an expressive code generator and an efficient search algorithm, we are able to
generate kernels that are comparable to heavily hand-optimized ones.
Since programmer time is expensive and machine time is getting cheaper,
we believe automatic optimization with real hardware and data in the loop will be the standard workflow
for inference deployment. TVM just provides such a solution.</p>

<h2 id="links">Links</h2>
<p>[1] benchmark: <a href="https://github.com/dmlc/tvm/tree/master/apps/benchmark">https://github.com/dmlc/tvm/tree/master/apps/benchmark</a><br />
[2] Tutorial about tuning for ARM CPU: <a href="https://docs.tvm.ai/tutorials/autotvm/tune_nnvm_arm.html">https://docs.tvm.ai/tutorials/autotvm/tune_nnvm_arm.html</a><br />
[3] Tutorial about tuning for Mobile GPU: <a href="https://docs.tvm.ai/tutorials/autotvm/tune_nnvm_mobile_gpu.html">https://docs.tvm.ai/tutorials/autotvm/tune_nnvm_mobile_gpu.html</a><br />
[4] Tutorial about tuning for NVIDIA/AMD GPU: <a href="https://docs.tvm.ai/tutorials/autotvm/tune_nnvm_cuda.html">https://docs.tvm.ai/tutorials/autotvm/tune_nnvm_cuda.html</a><br />
[5] Paper about AutoTVM: <a href="https://arxiv.org/abs/1805.08166">Learning to Optimize Tensor Program</a><br />
[6] Paper about Intel CPU (by AWS contributors) :  <a href="https://arxiv.org/abs/1809.02697">Optimizing CNN Model Inference on CPUs</a></p>]]></content><author><name>Lianmin Zheng, Eddie Yan, Tianqi Chen</name></author><category term="tvm" /><summary type="html"><![CDATA[Optimizing the performance of deep neural network on a diverse range of hardware platforms is still a hard problem for AI developers. In terms of system support, we are facing a many-to-many problem here: deploying trained models from multiple frontends (e.g. Tensorflow, ONNX, MXNet) to multiple hardware platforms (e.g. CPU, GPU, Accelerators). The most performance critical part of this problem is obtaining high performance kernel implementations for growing model architectures and hardware platforms.]]></summary></entry><entry><title type="html">Optimizing Mobile Deep Learning on ARM GPU with TVM</title><link href="https://lmzheng.net/posts/2018/01/opt-arm-mali" rel="alternate" type="text/html" title="Optimizing Mobile Deep Learning on ARM GPU with TVM" /><published>2018-01-15T00:00:00-08:00</published><updated>2018-01-15T00:00:00-08:00</updated><id>https://lmzheng.net/posts/2018/01/opt-mali-gpu</id><content type="html" xml:base="https://lmzheng.net/posts/2018/01/opt-arm-mali"><![CDATA[<p>With the great success of deep learning, the demand for
deploying deep neural networks to mobile devices is growing rapidly.
Similar to what we do in desktop platforms, utilizing GPU in mobile devices
can benefit both inference speed and energy efficiency. However, most
existing deep learning frameworks do not support mobile GPU very well.
The difficulty lies at the difference between mobile GPU architecture and
desktop GPU architecture. It means special effort is required for optimizing on
mobile GPU. The non-trivial extra work eventually results in the poor support
of mobile GPU in most deep learning frameworks.</p>

<p>TVM addresses the difficulty of deploying for different hardwares by
introducing an unified IR stack, with which the optimization for different
hardwares can be done easily.  In this post, we show how we use
<a href="http://tvmlang.org/2017/08/17/tvm-release-announcement.html">TVM</a>/<a href="http://tvmlang.org/2017/10/06/nnvm-compiler-announcement.html">NNVM</a> to
generate efficient kernels for ARM Mali GPU and do end-to-end compilation.
In our test on Mali-T860 MP4, compared with
<a href="https://developer.arm.com/technologies/compute-library">Arm Compute Library</a>,
our method is 1.4x faster on VGG-16 and 2.2x faster on MobileNet.
Both graph-level and operator-level optimization contribute
to this speed up.</p>

<p style="text-align: center"><img src="/images/opt-mali/end2end.png" alt="image" width="95%" /></p>

<center> Figure. Inference Speed of Different Backends on ImageNet</center>
<p></p>

<h1 id="mali-midgrad-gpu">Mali Midgrad GPU</h1>
<p>We will use Firefly-RK3399 with Mali-T860 MP4 as our test environment,
so we mainly focus on Mali T8xx below.</p>

<h2 id="architecture">Architecture</h2>
<p>Figure 1 is an overview of the Mali Architecture on T860 and T880.
The GPUs are scalable up to 16 coherent shader cores. Inside each
shader core, there are 2 or 3 arithmetic pipelines, 1 load/store pipeline
and 1 texture pipeline (so-called TriPipe). The ALU in each arithmetic
pipeline has four 128-bit vector units and one scalar units.</p>

<p>We use OpenCL for GPU computing. When mapping to OpenCL model, each
shader core executes one or several work groups. Each shader core supports
up to 384 concurrently executing threads. Each work item in OpenCL
typically maps to a single thread on a Mali GPU.
The Mali GPUs use a VLIW (Very Long Instruction Word) architecture.
Each instruction word contains multiple operations. The Mali GPUs
also use SIMD, so that most arithmetic instructions operate on
multiple data elements simultaneously. <sup>[1]</sup></p>

<center> <img width="50%" src="/images/opt-mali/mali-arch.png" /> </center>
<center> Figure 1. Mali T860 and T880 (source <sup>[2]</sup>) </center>

<h2 id="difference-with-nvidias-gpus">Difference with NVIDIA’s GPUs</h2>
<p>Here are some differences that we should concern when writing OpenCL
code for Mali GPUs, compared with writing for NVIDIA’s GPUs.</p>
<ul>
  <li>Mali GPUs use an unified global memory. In NVIDIA’s GPUs, we usually
copy data to shared memory, because NVIDIA’s GPUs have physically
separate global memory, shared memory and register. In Mali, this copy
does not improve performance and can be removed. Besides, Mali GPUs
usually share the global memory with CPU, so there is no need for copying
between CPU and GPU.</li>
  <li>Mali Midgrad GPUs are based on SIMD (Single Instruction Multiple Data)
and need explicit vectorization. In NVIDIA CUDA, parallelism is
achieved by SIMT (Single Instruction Multiple Thread), which does
not require explicit vectorization. But also notice that the newer
Mali Bitfrost GPUs are based on quad-style vectorization and does not
require explicit vectorization.</li>
  <li>All threads in Mali GPUs have individual program counters. It means
the <code class="language-plaintext highlighter-rouge">warp size</code> is 1, so that branch divergence is not a major problem.</li>
</ul>

<h1 id="optimization--convolution-as-example">Optimization : Convolution as Example</h1>
<p>The convolution layer is the core of most deep neural networks and
takes most of the computation time. So we take the convolution layer
as example to demonstrate how common optimization techniques like
packing, tiling, unrolling and vectorization are applied in TVM.</p>

<h2 id="im2col-with-gemm">Im2Col with GEMM</h2>
<p>A well-known algorithm for convolution layer is <a href="https://petewarden.com/2015/04/20/why-gemm-is-at-the-heart-of-deep-learning/">im2col</a>,
which converts the little 3D input cubes to columns of a matrix and
perform a GEMM. The advantage of this method is easy utilization of
highly optimized BLAS library.  However, the memory redundancy
(9x memory for 3x3 kernel) is awful.</p>

<h2 id="spatial-packing">Spatial Packing</h2>
<p>Instead, we adopt a method to calculate the convolution, and apply the
optimization techniques step by step. A convolution layer in VGG-16
is used as tuning case, whose configuration is listed below.
We assume the batch size is 1 for inference.</p>

<table>
  <thead>
    <tr>
      <th>Input Shape</th>
      <th>Output Shape</th>
      <th>Kernel Size</th>
      <th>Stride</th>
      <th>Padding</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>56x56x256</td>
      <td>56x56x256</td>
      <td>3x3</td>
      <td>(1, 1)</td>
      <td>(1, 1)</td>
    </tr>
  </tbody>
</table>

<p>As a baseline, we also list the performance of this layer in
Arm Compute Library.</p>

<table>
  <thead>
    <tr>
      <th>Kernel</th>
      <th>Cost (second)</th>
      <th>GFLOPS</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>GEMM method in ARMComputeLib</td>
      <td>0.1821</td>
      <td>20.3111</td>
    </tr>
  </tbody>
</table>

<h3 id="declare-the-computation-tiling-and-packing">Declare the computation: tiling and packing</h3>
<p>Tiling and packing are two methods intended for better memory access.
Tiling separates the whole computation into small blocks for better
datareuse.  Packing re-layouts the input matrices according to the
tiling so that we can access the memory sequentially, which reduces
cache miss rate.</p>

<p>We do tiling on the width dimension of the input image and CO dimension
of the filter matrix.  This is described by <code class="language-plaintext highlighter-rouge">tvm.compute</code>.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># set tiling factor
</span><span class="n">VH</span> <span class="o">=</span> <span class="mi">1</span>
<span class="n">VW</span> <span class="o">=</span> <span class="n">VC</span> <span class="o">=</span> <span class="mi">4</span>

<span class="c1"># get input shape
</span> <span class="n">_</span><span class="p">,</span> <span class="n">CI</span><span class="p">,</span> <span class="n">IH</span><span class="p">,</span> <span class="n">IW</span> <span class="o">=</span> <span class="n">data</span><span class="p">.</span><span class="n">shape</span>
<span class="n">CO</span><span class="p">,</span> <span class="n">CI</span><span class="p">,</span> <span class="n">KH</span><span class="p">,</span> <span class="n">KW</span> <span class="o">=</span> <span class="n">kernel</span><span class="p">.</span><span class="n">shape</span>
<span class="n">TH</span> <span class="o">=</span> <span class="n">IH</span> <span class="o">+</span> <span class="mi">2</span> <span class="o">*</span> <span class="n">H_PAD</span>
<span class="n">TW</span> <span class="o">=</span> <span class="n">IW</span> <span class="o">+</span> <span class="mi">2</span> <span class="o">*</span> <span class="n">W_PAD</span>

<span class="c1"># calc output shape
</span><span class="n">OH</span> <span class="o">=</span> <span class="p">(</span><span class="n">IH</span> <span class="o">+</span> <span class="mi">2</span><span class="o">*</span><span class="n">H_PAD</span> <span class="o">-</span> <span class="n">KH</span><span class="p">)</span> <span class="o">//</span> <span class="n">H_STR</span> <span class="o">+</span> <span class="mi">1</span>
<span class="n">OW</span> <span class="o">=</span> <span class="p">(</span><span class="n">IW</span> <span class="o">+</span> <span class="mi">2</span><span class="o">*</span><span class="n">W_PAD</span> <span class="o">-</span> <span class="n">KW</span><span class="p">)</span> <span class="o">//</span> <span class="n">W_STR</span> <span class="o">+</span> <span class="mi">1</span>

<span class="c1"># data shape after packing
</span><span class="n">dvshape</span> <span class="o">=</span> <span class="p">(</span><span class="n">N</span><span class="p">,</span> <span class="n">TH</span> <span class="o">//</span> <span class="p">(</span><span class="n">VH</span><span class="o">*</span><span class="n">H_STRIDE</span><span class="p">),</span> <span class="n">TW</span> <span class="o">//</span> <span class="p">(</span><span class="n">VW</span><span class="o">*</span><span class="n">W_STRIDE</span><span class="p">),</span> <span class="n">CI</span><span class="p">,</span> <span class="n">VH</span><span class="o">*</span><span class="n">H_STRIDE</span><span class="o">+</span><span class="n">HCAT</span><span class="p">,</span> <span class="n">VW</span><span class="o">*</span><span class="n">W_STRIDE</span><span class="o">+</span><span class="n">WCAT</span><span class="p">)</span>

<span class="c1"># kernel shape after packing
</span><span class="n">kvshape</span> <span class="o">=</span> <span class="p">(</span><span class="n">CO</span> <span class="o">//</span> <span class="n">VC</span><span class="p">,</span> <span class="n">CI</span><span class="p">,</span> <span class="n">KH</span><span class="p">,</span> <span class="n">KW</span><span class="p">,</span> <span class="n">VC</span><span class="p">)</span>

<span class="n">ovshape</span> <span class="o">=</span> <span class="p">(</span><span class="n">N</span><span class="p">,</span> <span class="n">CO</span> <span class="o">//</span> <span class="n">VC</span><span class="p">,</span> <span class="n">OH</span> <span class="o">//</span> <span class="n">VH</span><span class="p">,</span> <span class="n">OW</span> <span class="o">//</span> <span class="n">VW</span><span class="p">,</span> <span class="n">VH</span><span class="p">,</span> <span class="n">VW</span><span class="p">,</span> <span class="n">VC</span><span class="p">)</span>
<span class="n">oshape</span> <span class="o">=</span> <span class="p">(</span><span class="n">N</span><span class="p">,</span> <span class="n">CO</span><span class="p">,</span> <span class="n">OH</span><span class="p">,</span> <span class="n">OW</span><span class="p">)</span>

<span class="c1"># define packing
</span><span class="n">data_vec</span> <span class="o">=</span> <span class="n">tvm</span><span class="p">.</span><span class="n">compute</span><span class="p">(</span><span class="n">dvshape</span><span class="p">,</span> <span class="k">lambda</span> <span class="n">n</span><span class="p">,</span> <span class="n">h</span><span class="p">,</span> <span class="n">w</span><span class="p">,</span> <span class="n">ci</span><span class="p">,</span> <span class="n">vh</span><span class="p">,</span> <span class="n">vw</span><span class="p">:</span>
    <span class="n">data_pad</span><span class="p">[</span><span class="n">n</span><span class="p">][</span><span class="n">ci</span><span class="p">][</span><span class="n">h</span><span class="o">*</span><span class="n">VH</span><span class="o">*</span><span class="n">H_STRIDE</span><span class="o">+</span><span class="n">vh</span><span class="p">][</span><span class="n">w</span><span class="o">*</span><span class="n">VW</span><span class="o">*</span><span class="n">W_STRIDE</span><span class="o">+</span><span class="n">vw</span><span class="p">],</span> <span class="n">name</span><span class="o">=</span><span class="s">'data_vec'</span><span class="p">)</span>

<span class="n">kernel_vec</span> <span class="o">=</span> <span class="n">tvm</span><span class="p">.</span><span class="n">compute</span><span class="p">(</span><span class="n">kvshape</span><span class="p">,</span> <span class="k">lambda</span> <span class="n">co</span><span class="p">,</span> <span class="n">ci</span><span class="p">,</span> <span class="n">kh</span><span class="p">,</span> <span class="n">kw</span><span class="p">,</span> <span class="n">vc</span><span class="p">:</span>
    <span class="n">kernel</span><span class="p">[</span><span class="n">co</span><span class="o">*</span><span class="n">VC</span><span class="o">+</span><span class="n">vc</span><span class="p">][</span><span class="n">ci</span><span class="p">][</span><span class="n">kh</span><span class="p">][</span><span class="n">kw</span><span class="p">],</span> <span class="n">name</span><span class="o">=</span><span class="s">'kernel_vec'</span><span class="p">)</span>

<span class="c1"># define convolution
</span><span class="n">ci</span> <span class="o">=</span> <span class="n">tvm</span><span class="p">.</span><span class="n">reduce_axis</span><span class="p">((</span><span class="mi">0</span><span class="p">,</span> <span class="n">CI</span><span class="p">),</span> <span class="n">name</span><span class="o">=</span><span class="s">'ci'</span><span class="p">)</span>
<span class="n">kh</span> <span class="o">=</span> <span class="n">tvm</span><span class="p">.</span><span class="n">reduce_axis</span><span class="p">((</span><span class="mi">0</span><span class="p">,</span> <span class="n">KH</span><span class="p">),</span> <span class="n">name</span><span class="o">=</span><span class="s">'kh'</span><span class="p">)</span>
<span class="n">kw</span> <span class="o">=</span> <span class="n">tvm</span><span class="p">.</span><span class="n">reduce_axis</span><span class="p">((</span><span class="mi">0</span><span class="p">,</span> <span class="n">KW</span><span class="p">),</span> <span class="n">name</span><span class="o">=</span><span class="s">'kw'</span><span class="p">)</span>

<span class="n">conv</span> <span class="o">=</span> <span class="n">tvm</span><span class="p">.</span><span class="n">compute</span><span class="p">(</span><span class="n">ovshape</span><span class="p">,</span> <span class="k">lambda</span> <span class="n">n</span><span class="p">,</span> <span class="n">co</span><span class="p">,</span> <span class="n">h</span><span class="p">,</span> <span class="n">w</span><span class="p">,</span> <span class="n">vh</span><span class="p">,</span> <span class="n">vw</span><span class="p">,</span> <span class="n">vc</span><span class="p">:</span>
    <span class="n">tvm</span><span class="p">.</span><span class="nb">sum</span><span class="p">(</span><span class="n">data_vec</span><span class="p">[</span><span class="n">n</span><span class="p">,</span> <span class="n">h</span><span class="p">,</span> <span class="n">w</span><span class="p">,</span> <span class="n">ci</span><span class="p">,</span> <span class="n">vh</span><span class="o">*</span><span class="n">H_STRIDE</span><span class="o">+</span><span class="n">kh</span><span class="p">,</span> <span class="n">vw</span><span class="o">*</span><span class="n">W_STRIDE</span><span class="o">+</span><span class="n">kw</span><span class="p">].</span><span class="n">astype</span><span class="p">(</span><span class="n">out_dtype</span><span class="p">)</span> <span class="o">*</span>
            <span class="n">kernel_vec</span><span class="p">[</span><span class="n">co</span><span class="p">,</span> <span class="n">ci</span><span class="p">,</span> <span class="n">kh</span><span class="p">,</span> <span class="n">kw</span><span class="p">,</span> <span class="n">vc</span><span class="p">].</span><span class="n">astype</span><span class="p">(</span><span class="n">out_dtype</span><span class="p">),</span>
            <span class="n">axis</span><span class="o">=</span><span class="p">[</span><span class="n">ci</span><span class="p">,</span> <span class="n">kh</span><span class="p">,</span> <span class="n">kw</span><span class="p">]),</span> <span class="n">name</span><span class="o">=</span><span class="s">'conv'</span><span class="p">)</span>

<span class="c1"># unpack to correct layout
</span><span class="n">output</span> <span class="o">=</span> <span class="n">tvm</span><span class="p">.</span><span class="n">compute</span><span class="p">(</span><span class="n">oshape</span><span class="p">,</span> <span class="k">lambda</span> <span class="n">n</span><span class="p">,</span> <span class="n">co</span><span class="p">,</span> <span class="n">h</span><span class="p">,</span> <span class="n">w</span><span class="p">:</span>
                     <span class="n">conv</span><span class="p">[</span><span class="n">n</span><span class="p">][</span><span class="n">co</span><span class="o">//</span><span class="n">VC</span><span class="p">][</span><span class="n">h</span><span class="o">/</span><span class="n">VH</span><span class="p">][</span><span class="n">w</span><span class="o">//</span><span class="n">VW</span><span class="p">][</span><span class="n">h</span><span class="o">%</span><span class="n">VH</span><span class="p">][</span><span class="n">w</span><span class="o">%</span><span class="n">VW</span><span class="p">][</span><span class="n">co</span><span class="o">%</span><span class="n">VC</span><span class="p">],</span>
                     <span class="n">name</span><span class="o">=</span><span class="s">'output_unpack'</span><span class="p">,</span> <span class="n">tag</span><span class="o">=</span><span class="s">'direct_conv_output'</span><span class="p">)</span>
</code></pre></div></div>

<p>We can inspect the defined IR by</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">print</span><span class="p">(</span><span class="n">tvm</span><span class="p">.</span><span class="n">lower</span><span class="p">(</span><span class="n">s</span><span class="p">,</span> <span class="p">[</span><span class="n">data</span><span class="p">,</span> <span class="n">kernel</span><span class="p">,</span> <span class="n">output</span><span class="p">],</span> <span class="n">simple_mode</span><span class="o">=</span><span class="bp">True</span><span class="p">))</span>
</code></pre></div></div>
<p>I pick the convolution part here.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>produce conv {
  for (co, 0, 64) {
    for (h, 0, 56) {
      for (w, 0, 14) {
        for (vw.init, 0, 4) {
          for (vc.init, 0, 4) {
            conv[((((((((co*56) + h)*14) + w)*4) + vw.init)*4) + vc.init)] = 0.000000f
          }
        }
        for (ci, 0, 256) {
          for (kh, 0, 3) {
            for (kw, 0, 3) {
              for (vw, 0, 4) {
                for (vc, 0, 4) {
                  conv[((((((((co*56) + h)*14) + w)*4) + vw)*4) + vc)] = (conv[((((((((co*56) + h)*14) + w)*4) + vw)*4) + vc)] + (data_vec[(((((((((h*14) + w)*256) + ci)*3) + kh)*6) + kw) + vw)]*kernel_vec[((((((((co*256) + ci)*3) + kh)*3) + kw)*4) + vc)]))
                }
              }
            }
          }
        }
      }
    }
  }
}
</code></pre></div></div>

<h3 id="kernel-1-bind-thread">Kernel 1: bind thread</h3>
<p>In TVM, we declare the computation at first and then <em>schedule</em> it.
This mechanism decouples the algorithm and implementation detail. (This idea
is from <a href="http://halide-lang.org/">Halide</a>).</p>

<p>The following schedule simply binds axes to GPU threads, so that
our code can run on Mali GPU.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># helper function for binding thread
</span><span class="k">def</span> <span class="nf">tile_and_bind3d</span><span class="p">(</span><span class="n">s</span><span class="p">,</span> <span class="n">tensor</span><span class="p">,</span> <span class="n">z</span><span class="p">,</span> <span class="n">y</span><span class="p">,</span> <span class="n">x</span><span class="p">,</span> <span class="n">z_factor</span><span class="o">=</span><span class="mi">2</span><span class="p">,</span> <span class="n">y_factor</span><span class="o">=</span><span class="bp">None</span><span class="p">,</span> <span class="n">x_factor</span><span class="o">=</span><span class="bp">None</span><span class="p">):</span>
    <span class="s">""" tile and bind 3d """</span>
    <span class="n">y_factor</span> <span class="o">=</span> <span class="n">y_factor</span> <span class="ow">or</span> <span class="n">z_factor</span>
    <span class="n">x_factor</span> <span class="o">=</span> <span class="n">x_factor</span> <span class="ow">or</span> <span class="n">y_factor</span>
    <span class="n">zo</span><span class="p">,</span> <span class="n">zi</span> <span class="o">=</span> <span class="n">s</span><span class="p">[</span><span class="n">tensor</span><span class="p">].</span><span class="n">split</span><span class="p">(</span><span class="n">z</span><span class="p">,</span> <span class="n">z_factor</span><span class="p">)</span>
    <span class="n">yo</span><span class="p">,</span> <span class="n">yi</span> <span class="o">=</span> <span class="n">s</span><span class="p">[</span><span class="n">tensor</span><span class="p">].</span><span class="n">split</span><span class="p">(</span><span class="n">y</span><span class="p">,</span> <span class="n">y_factor</span><span class="p">)</span>
    <span class="n">xo</span><span class="p">,</span> <span class="n">xi</span> <span class="o">=</span> <span class="n">s</span><span class="p">[</span><span class="n">tensor</span><span class="p">].</span><span class="n">split</span><span class="p">(</span><span class="n">x</span><span class="p">,</span> <span class="n">x_factor</span><span class="p">)</span>
    <span class="n">s</span><span class="p">[</span><span class="n">tensor</span><span class="p">].</span><span class="n">bind</span><span class="p">(</span><span class="n">zo</span><span class="p">,</span> <span class="n">tvm</span><span class="p">.</span><span class="n">thread_axis</span><span class="p">(</span><span class="s">"blockIdx.z"</span><span class="p">))</span>
    <span class="n">s</span><span class="p">[</span><span class="n">tensor</span><span class="p">].</span><span class="n">bind</span><span class="p">(</span><span class="n">zi</span><span class="p">,</span> <span class="n">tvm</span><span class="p">.</span><span class="n">thread_axis</span><span class="p">(</span><span class="s">"threadIdx.z"</span><span class="p">))</span>
    <span class="n">s</span><span class="p">[</span><span class="n">tensor</span><span class="p">].</span><span class="n">bind</span><span class="p">(</span><span class="n">yo</span><span class="p">,</span> <span class="n">tvm</span><span class="p">.</span><span class="n">thread_axis</span><span class="p">(</span><span class="s">"blockIdx.y"</span><span class="p">))</span>
    <span class="n">s</span><span class="p">[</span><span class="n">tensor</span><span class="p">].</span><span class="n">bind</span><span class="p">(</span><span class="n">yi</span><span class="p">,</span> <span class="n">tvm</span><span class="p">.</span><span class="n">thread_axis</span><span class="p">(</span><span class="s">"threadIdx.y"</span><span class="p">))</span>
    <span class="n">s</span><span class="p">[</span><span class="n">tensor</span><span class="p">].</span><span class="n">bind</span><span class="p">(</span><span class="n">xo</span><span class="p">,</span> <span class="n">tvm</span><span class="p">.</span><span class="n">thread_axis</span><span class="p">(</span><span class="s">"blockIdx.x"</span><span class="p">))</span>
    <span class="n">s</span><span class="p">[</span><span class="n">tensor</span><span class="p">].</span><span class="n">bind</span><span class="p">(</span><span class="n">xi</span><span class="p">,</span> <span class="n">tvm</span><span class="p">.</span><span class="n">thread_axis</span><span class="p">(</span><span class="s">"threadIdx.x"</span><span class="p">))</span>

<span class="c1"># set tunable parameter
</span><span class="n">num_thread</span> <span class="o">=</span> <span class="mi">8</span>

<span class="c1"># schedule data packing
</span><span class="n">_</span><span class="p">,</span> <span class="n">h</span><span class="p">,</span> <span class="n">w</span><span class="p">,</span> <span class="n">ci</span><span class="p">,</span> <span class="n">vh</span><span class="p">,</span> <span class="n">vw</span> <span class="o">=</span> <span class="n">s</span><span class="p">[</span><span class="n">data_vec</span><span class="p">].</span><span class="n">op</span><span class="p">.</span><span class="n">axis</span>
<span class="n">tile_and_bind3d</span><span class="p">(</span><span class="n">s</span><span class="p">,</span> <span class="n">data_vec</span><span class="p">,</span> <span class="n">h</span><span class="p">,</span> <span class="n">w</span><span class="p">,</span> <span class="n">ci</span><span class="p">,</span> <span class="mi">1</span><span class="p">)</span>

<span class="c1"># schedule kernel packing
</span><span class="n">co</span><span class="p">,</span> <span class="n">ci</span><span class="p">,</span> <span class="n">kh</span><span class="p">,</span> <span class="n">kw</span><span class="p">,</span> <span class="n">vc</span> <span class="o">=</span> <span class="n">s</span><span class="p">[</span><span class="n">kernel_vec</span><span class="p">].</span><span class="n">op</span><span class="p">.</span><span class="n">axis</span>
<span class="n">tile_and_bind</span><span class="p">(</span><span class="n">s</span><span class="p">,</span> <span class="n">kernel_vec</span><span class="p">,</span> <span class="n">co</span><span class="p">,</span> <span class="n">ci</span><span class="p">,</span> <span class="mi">1</span><span class="p">)</span>

<span class="c1"># schedule conv
</span><span class="n">_</span><span class="p">,</span> <span class="n">c</span><span class="p">,</span> <span class="n">h</span><span class="p">,</span> <span class="n">w</span><span class="p">,</span> <span class="n">vh</span><span class="p">,</span> <span class="n">vw</span><span class="p">,</span> <span class="n">vc</span> <span class="o">=</span> <span class="n">s</span><span class="p">[</span><span class="n">conv</span><span class="p">].</span><span class="n">op</span><span class="p">.</span><span class="n">axis</span>
<span class="n">kc</span><span class="p">,</span> <span class="n">kh</span><span class="p">,</span> <span class="n">kw</span> <span class="o">=</span> <span class="n">s</span><span class="p">[</span><span class="n">conv</span><span class="p">].</span><span class="n">op</span><span class="p">.</span><span class="n">reduce_axis</span>

<span class="n">s</span><span class="p">[</span><span class="n">conv</span><span class="p">].</span><span class="n">reorder</span><span class="p">(</span><span class="n">_</span><span class="p">,</span> <span class="n">c</span><span class="p">,</span> <span class="n">h</span><span class="p">,</span> <span class="n">w</span><span class="p">,</span> <span class="n">vh</span><span class="p">,</span> <span class="n">kc</span><span class="p">,</span> <span class="n">kh</span><span class="p">,</span> <span class="n">kw</span><span class="p">,</span> <span class="n">vw</span><span class="p">,</span> <span class="n">vc</span><span class="p">)</span>
<span class="n">tile_and_bind3d</span><span class="p">(</span><span class="n">s</span><span class="p">,</span> <span class="n">conv</span><span class="p">,</span> <span class="n">c</span><span class="p">,</span> <span class="n">h</span><span class="p">,</span> <span class="n">w</span><span class="p">,</span> <span class="n">num_thread</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">1</span><span class="p">)</span>

<span class="n">_</span><span class="p">,</span> <span class="n">co</span><span class="p">,</span> <span class="n">oh</span><span class="p">,</span> <span class="n">ow</span> <span class="o">=</span> <span class="n">s</span><span class="p">[</span><span class="n">output</span><span class="p">].</span><span class="n">op</span><span class="p">.</span><span class="n">axis</span>
<span class="n">tile_and_bind3d</span><span class="p">(</span><span class="n">s</span><span class="p">,</span> <span class="n">output</span><span class="p">,</span> <span class="n">co</span><span class="p">,</span> <span class="n">oh</span><span class="p">,</span> <span class="n">ow</span><span class="p">,</span> <span class="n">num_thread</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">1</span><span class="p">)</span>
</code></pre></div></div>

<p>With this schedule, our code can run now, but the performance is terrible.</p>

<table>
  <thead>
    <tr>
      <th>Kernel</th>
      <th>Cost (second)</th>
      <th>GFLOPS</th>
      <th>speedup</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>GEMM method in ARMComputeLib</td>
      <td>0.1821</td>
      <td>20.3111</td>
      <td>1x</td>
    </tr>
    <tr>
      <td>Kernel 1: simple bind</td>
      <td>5.6154</td>
      <td>0.6588</td>
      <td>0.03x</td>
    </tr>
  </tbody>
</table>

<h3 id="kernel-2-unrolling">Kernel 2: unrolling</h3>
<p>Loop unrolling can reduce the instructions for loop control, reduce
branch penalties and hide latency in reading memory.
In TVM, this can be done easily by calling <code class="language-plaintext highlighter-rouge">s.unroll(axis)</code></p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># set tunable parameter
</span><span class="n">num_thread</span> <span class="o">=</span> <span class="mi">8</span>

<span class="c1"># schedule data packing
</span><span class="n">_</span><span class="p">,</span> <span class="n">h</span><span class="p">,</span> <span class="n">w</span><span class="p">,</span> <span class="n">ci</span><span class="p">,</span> <span class="n">vh</span><span class="p">,</span> <span class="n">vw</span> <span class="o">=</span> <span class="n">s</span><span class="p">[</span><span class="n">data_vec</span><span class="p">].</span><span class="n">op</span><span class="p">.</span><span class="n">axis</span>
<span class="n">tile_and_bind3d</span><span class="p">(</span><span class="n">s</span><span class="p">,</span> <span class="n">data_vec</span><span class="p">,</span> <span class="n">h</span><span class="p">,</span> <span class="n">w</span><span class="p">,</span> <span class="n">ci</span><span class="p">,</span> <span class="mi">1</span><span class="p">)</span>

<span class="s">"""!! ADD UNROLL HERE !!"""</span>
<span class="n">s</span><span class="p">[</span><span class="n">data_vec</span><span class="p">].</span><span class="n">unroll</span><span class="p">(</span><span class="n">vw</span><span class="p">)</span>

<span class="c1"># schedule kernel packing
</span><span class="n">co</span><span class="p">,</span> <span class="n">ci</span><span class="p">,</span> <span class="n">kh</span><span class="p">,</span> <span class="n">kw</span><span class="p">,</span> <span class="n">vc</span> <span class="o">=</span> <span class="n">s</span><span class="p">[</span><span class="n">kernel_vec</span><span class="p">].</span><span class="n">op</span><span class="p">.</span><span class="n">axis</span>
<span class="n">tile_and_bind</span><span class="p">(</span><span class="n">s</span><span class="p">,</span> <span class="n">kernel_vec</span><span class="p">,</span> <span class="n">co</span><span class="p">,</span> <span class="n">ci</span><span class="p">,</span> <span class="mi">1</span><span class="p">)</span>

<span class="s">"""!! ADD UNROLL HERE !!"""</span>
<span class="n">s</span><span class="p">[</span><span class="n">kernel_vec</span><span class="p">].</span><span class="n">unroll</span><span class="p">(</span><span class="n">kh</span><span class="p">)</span>
<span class="n">s</span><span class="p">[</span><span class="n">kernel_vec</span><span class="p">].</span><span class="n">unroll</span><span class="p">(</span><span class="n">kw</span><span class="p">)</span>
<span class="n">s</span><span class="p">[</span><span class="n">kernel_vec</span><span class="p">].</span><span class="n">unroll</span><span class="p">(</span><span class="n">vc</span><span class="p">)</span>

<span class="c1"># schedule conv
</span><span class="n">_</span><span class="p">,</span> <span class="n">c</span><span class="p">,</span> <span class="n">h</span><span class="p">,</span> <span class="n">w</span><span class="p">,</span> <span class="n">vh</span><span class="p">,</span> <span class="n">vw</span><span class="p">,</span> <span class="n">vc</span> <span class="o">=</span> <span class="n">s</span><span class="p">[</span><span class="n">conv</span><span class="p">].</span><span class="n">op</span><span class="p">.</span><span class="n">axis</span>
<span class="n">kc</span><span class="p">,</span> <span class="n">kh</span><span class="p">,</span> <span class="n">kw</span> <span class="o">=</span> <span class="n">s</span><span class="p">[</span><span class="n">conv</span><span class="p">].</span><span class="n">op</span><span class="p">.</span><span class="n">reduce_axis</span>

<span class="n">s</span><span class="p">[</span><span class="n">conv</span><span class="p">].</span><span class="n">reorder</span><span class="p">(</span><span class="n">_</span><span class="p">,</span> <span class="n">c</span><span class="p">,</span> <span class="n">h</span><span class="p">,</span> <span class="n">w</span><span class="p">,</span> <span class="n">vh</span><span class="p">,</span> <span class="n">kc</span><span class="p">,</span> <span class="n">kh</span><span class="p">,</span> <span class="n">kw</span><span class="p">,</span> <span class="n">vw</span><span class="p">,</span> <span class="n">vc</span><span class="p">)</span>
<span class="n">tile_and_bind3d</span><span class="p">(</span><span class="n">s</span><span class="p">,</span> <span class="n">conv</span><span class="p">,</span> <span class="n">c</span><span class="p">,</span> <span class="n">h</span><span class="p">,</span> <span class="n">w</span><span class="p">,</span> <span class="n">num_thread</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">1</span><span class="p">)</span>

<span class="s">"""!! ADD UNROLL HERE !!"""</span>
<span class="n">s</span><span class="p">[</span><span class="n">conv</span><span class="p">].</span><span class="n">unroll</span><span class="p">(</span><span class="n">kh</span><span class="p">)</span>
<span class="n">s</span><span class="p">[</span><span class="n">conv</span><span class="p">].</span><span class="n">unroll</span><span class="p">(</span><span class="n">kw</span><span class="p">)</span>
<span class="n">s</span><span class="p">[</span><span class="n">conv</span><span class="p">].</span><span class="n">unroll</span><span class="p">(</span><span class="n">vw</span><span class="p">)</span>
<span class="n">s</span><span class="p">[</span><span class="n">conv</span><span class="p">].</span><span class="n">unroll</span><span class="p">(</span><span class="n">vc</span><span class="p">)</span>

<span class="n">_</span><span class="p">,</span> <span class="n">co</span><span class="p">,</span> <span class="n">oh</span><span class="p">,</span> <span class="n">ow</span> <span class="o">=</span> <span class="n">s</span><span class="p">[</span><span class="n">output</span><span class="p">].</span><span class="n">op</span><span class="p">.</span><span class="n">axis</span>
<span class="n">tile_and_bind3d</span><span class="p">(</span><span class="n">s</span><span class="p">,</span> <span class="n">output</span><span class="p">,</span> <span class="n">co</span><span class="p">,</span> <span class="n">oh</span><span class="p">,</span> <span class="n">ow</span><span class="p">,</span> <span class="n">num_thread</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">1</span><span class="p">)</span>
</code></pre></div></div>

<table>
  <thead>
    <tr>
      <th>Kernel</th>
      <th>Cost (second)</th>
      <th>GFLOPS</th>
      <th>speedup</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>GEMM method in ARMComputeLib</td>
      <td>0.1821</td>
      <td>20.3111</td>
      <td>1x</td>
    </tr>
    <tr>
      <td>Kernel 1: simple bind</td>
      <td>5.6154</td>
      <td>0.6588</td>
      <td>0.03x</td>
    </tr>
    <tr>
      <td>Kernel 2: + unrolling</td>
      <td>0.3707</td>
      <td>9.9796</td>
      <td>0.49x</td>
    </tr>
  </tbody>
</table>

<h3 id="kernel3-vectorization">Kernel3: vectorization</h3>
<p>As mentioned before, we need to do vectorization explictly
 in order to achieve the best performance on Mali GPU.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># set tunable parameter
</span><span class="n">num_thread</span> <span class="o">=</span> <span class="mi">8</span>

<span class="c1"># schedule data packing
</span><span class="n">_</span><span class="p">,</span> <span class="n">h</span><span class="p">,</span> <span class="n">w</span><span class="p">,</span> <span class="n">ci</span><span class="p">,</span> <span class="n">vh</span><span class="p">,</span> <span class="n">vw</span> <span class="o">=</span> <span class="n">s</span><span class="p">[</span><span class="n">data_vec</span><span class="p">].</span><span class="n">op</span><span class="p">.</span><span class="n">axis</span>
<span class="n">tile_and_bind3d</span><span class="p">(</span><span class="n">s</span><span class="p">,</span> <span class="n">data_vec</span><span class="p">,</span> <span class="n">h</span><span class="p">,</span> <span class="n">w</span><span class="p">,</span> <span class="n">ci</span><span class="p">,</span> <span class="mi">1</span><span class="p">)</span>

<span class="c1"># unroll
</span><span class="n">s</span><span class="p">[</span><span class="n">data_vec</span><span class="p">].</span><span class="n">unroll</span><span class="p">(</span><span class="n">vw</span><span class="p">)</span>

<span class="c1"># schedule kernel packing
</span><span class="n">co</span><span class="p">,</span> <span class="n">ci</span><span class="p">,</span> <span class="n">kh</span><span class="p">,</span> <span class="n">kw</span><span class="p">,</span> <span class="n">vc</span> <span class="o">=</span> <span class="n">s</span><span class="p">[</span><span class="n">kernel_vec</span><span class="p">].</span><span class="n">op</span><span class="p">.</span><span class="n">axis</span>
<span class="n">tile_and_bind</span><span class="p">(</span><span class="n">s</span><span class="p">,</span> <span class="n">kernel_vec</span><span class="p">,</span> <span class="n">co</span><span class="p">,</span> <span class="n">ci</span><span class="p">,</span> <span class="mi">1</span><span class="p">)</span>

<span class="c1"># unroll
</span><span class="n">s</span><span class="p">[</span><span class="n">kernel_vec</span><span class="p">].</span><span class="n">unroll</span><span class="p">(</span><span class="n">kh</span><span class="p">)</span>
<span class="n">s</span><span class="p">[</span><span class="n">kernel_vec</span><span class="p">].</span><span class="n">unroll</span><span class="p">(</span><span class="n">kw</span><span class="p">)</span>
<span class="s">"""!! VECTORIZE HERE !!"""</span>
<span class="n">s</span><span class="p">[</span><span class="n">kernel_vec</span><span class="p">].</span><span class="n">vectorize</span><span class="p">(</span><span class="n">vc</span><span class="p">)</span>

<span class="c1"># schedule conv
</span><span class="n">_</span><span class="p">,</span> <span class="n">c</span><span class="p">,</span> <span class="n">h</span><span class="p">,</span> <span class="n">w</span><span class="p">,</span> <span class="n">vh</span><span class="p">,</span> <span class="n">vw</span><span class="p">,</span> <span class="n">vc</span> <span class="o">=</span> <span class="n">s</span><span class="p">[</span><span class="n">conv</span><span class="p">].</span><span class="n">op</span><span class="p">.</span><span class="n">axis</span>
<span class="n">kc</span><span class="p">,</span> <span class="n">kh</span><span class="p">,</span> <span class="n">kw</span> <span class="o">=</span> <span class="n">s</span><span class="p">[</span><span class="n">conv</span><span class="p">].</span><span class="n">op</span><span class="p">.</span><span class="n">reduce_axis</span>

<span class="n">s</span><span class="p">[</span><span class="n">conv</span><span class="p">].</span><span class="n">reorder</span><span class="p">(</span><span class="n">_</span><span class="p">,</span> <span class="n">c</span><span class="p">,</span> <span class="n">h</span><span class="p">,</span> <span class="n">w</span><span class="p">,</span> <span class="n">vh</span><span class="p">,</span> <span class="n">kc</span><span class="p">,</span> <span class="n">kh</span><span class="p">,</span> <span class="n">kw</span><span class="p">,</span> <span class="n">vw</span><span class="p">,</span> <span class="n">vc</span><span class="p">)</span>
<span class="n">tile_and_bind3d</span><span class="p">(</span><span class="n">s</span><span class="p">,</span> <span class="n">conv</span><span class="p">,</span> <span class="n">c</span><span class="p">,</span> <span class="n">h</span><span class="p">,</span> <span class="n">w</span><span class="p">,</span> <span class="n">num_thread</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">1</span><span class="p">)</span>

<span class="c1"># unroll
</span><span class="n">s</span><span class="p">[</span><span class="n">conv</span><span class="p">].</span><span class="n">unroll</span><span class="p">(</span><span class="n">kh</span><span class="p">)</span>
<span class="n">s</span><span class="p">[</span><span class="n">conv</span><span class="p">].</span><span class="n">unroll</span><span class="p">(</span><span class="n">kw</span><span class="p">)</span>
<span class="n">s</span><span class="p">[</span><span class="n">conv</span><span class="p">].</span><span class="n">unroll</span><span class="p">(</span><span class="n">vw</span><span class="p">)</span>
<span class="s">"""!! VECTORIZE HERE !!"""</span>
<span class="n">s</span><span class="p">[</span><span class="n">conv</span><span class="p">].</span><span class="n">vectorize</span><span class="p">(</span><span class="n">vc</span><span class="p">)</span>

<span class="n">_</span><span class="p">,</span> <span class="n">co</span><span class="p">,</span> <span class="n">oh</span><span class="p">,</span> <span class="n">ow</span> <span class="o">=</span> <span class="n">s</span><span class="p">[</span><span class="n">output</span><span class="p">].</span><span class="n">op</span><span class="p">.</span><span class="n">axis</span>
<span class="n">tile_and_bind3d</span><span class="p">(</span><span class="n">s</span><span class="p">,</span> <span class="n">output</span><span class="p">,</span> <span class="n">co</span><span class="p">,</span> <span class="n">oh</span><span class="p">,</span> <span class="n">ow</span><span class="p">,</span> <span class="n">num_thread</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">1</span><span class="p">)</span>
</code></pre></div></div>

<table>
  <thead>
    <tr>
      <th>Kernel</th>
      <th>Cost (second)</th>
      <th>GFLOPS</th>
      <th>speedup</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>GEMM method in ARMComputeLib</td>
      <td>0.1821</td>
      <td>20.3111</td>
      <td>1x</td>
    </tr>
    <tr>
      <td>Kernel 1: simple bind</td>
      <td>5.6154</td>
      <td>0.6588</td>
      <td>0.03x</td>
    </tr>
    <tr>
      <td>Kernel 2: + unrolling</td>
      <td>0.3707</td>
      <td>9.9796</td>
      <td>0.49x</td>
    </tr>
    <tr>
      <td>Kernel 3: + vectorization</td>
      <td>0.1304</td>
      <td>28.3679</td>
      <td>1.40x</td>
    </tr>
  </tbody>
</table>

<h3 id="how-to-set-the-tunable-parameter">How to set the tunable parameter</h3>
<p>As for the tunable parameters above, some can be calculated.
For the vectorized dimension <code class="language-plaintext highlighter-rouge">VC</code>, we should fill the 128-bit register,
so it can be set as 128/32=4 for float32 and 128/16=8 for float16.</p>

<p>But more often we cannot determine the optimal value, due to the
complicated runtime. We use grid search in TVM. It can be
done extremely effective since we write python code in TVM’s high-level
IR rather than direct OpenCL code.</p>

<h3 id="the-generated-opencl-code">The generated OpenCL code</h3>
<p>We can view the generated OpenCL code by</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">print</span><span class="p">(</span><span class="n">func</span><span class="p">.</span><span class="n">imported_modules</span><span class="p">[</span><span class="mi">0</span><span class="p">].</span><span class="n">get_source</span><span class="p">())</span>
</code></pre></div></div>
<p>The OpenCL code is too long to be pasted here, and it is hard to read due
to heavy unrolling. If interested, you can view it
<a href="https://github.com/merrymercy/tvm-mali/blob/master/data/kernels.cl">here</a>.</p>

<h1 id="end-to-end-benchmarking">End-to-End Benchmarking</h1>
<p>In this section, we compare the comprehensive performance between
different backends on some popular deep neural networks.
Our test environment is</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Firefly-RK3399 4G
CPU: dual-core Cortex-A72 + quad-core Cortex-A53
GPU: Mali-T860MP4

Arm Compute Library : v17.12
MXNet: v1.0.1
Openblas: v0.2.18
</code></pre></div></div>

<p>We use NNVM and TVM to do end-to-end compilation.</p>

<h2 id="performance">Performance</h2>

<p style="text-align: center"><img src="/images/opt-mali/end2end.png" alt="image" width="95%" /></p>

<center> Figure 2. Inference Speed of Different Backends on ImageNet</center>
<p></p>

<p>As shown in Figure 2, we test the inference speed on ImageNet.
On Firefly-RK3399, Mali GPU can be 2x ~ 4x faster than 6-core big.LITTLE CPU.
Our end-to-end pipeline is 1.4x ~ 2.2x faster than Arm Compute Library.
We try both GEMM and direct method of convolution layer in
Arm Compute Library, GEMM method is always faster than direct method
in these test cases, so we only plot the result of GEMM method.</p>

<p>Some results, like resnet18 on Arm Compute Library, are missing in the Figure 2.
It is because the graph runtime of Arm Compute Library does not support
skip connection currently and has a poor neon implementation of
depthwise convolution.  This also reflects the advantage of NNVM
software stack.</p>

<h2 id="half-precision-performance">Half-Precision Performance</h2>
<p>Precision in deep neural networks is not very important, especially
for the inference on mobile devices. Using low-precision arithmetic
can make the inference much faster. We also test the half-precision
floating number on Mali GPU.</p>

<table>
  <thead>
    <tr>
      <th>model</th>
      <th>backend</th>
      <th>Time Cost per Image (second)</th>
      <th>speed up to FP32</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>vgg16</td>
      <td>ACM-mali</td>
      <td>0.9694</td>
      <td>1.69</td>
    </tr>
    <tr>
      <td>vgg16</td>
      <td>TVM-mali</td>
      <td>0.6896</td>
      <td><strong>1.87x</strong></td>
    </tr>
    <tr>
      <td>MobileNet 1.0</td>
      <td>TVM-mali</td>
      <td>0.0479</td>
      <td>1.60x</td>
    </tr>
    <tr>
      <td>ResNet18</td>
      <td>TVM-mali</td>
      <td>0.1183</td>
      <td>1.73x</td>
    </tr>
  </tbody>
</table>

<center> Table 1. Inference Speed of FP16 on ImageNet</center>
<p></p>

<p>In theory, FP16 can both double peak compute and halve memory consumption,
so that doubling the speed. But it needs good input shape for
longer vectorization and fine-tuning some parameters.</p>

<h2 id="further-work-on-mobile-devices">Further Work on Mobile Devices</h2>
<p>We should admit that there is still some room for improvement,
mainly at the graph level, such as model compression and weight prelayout.
Further improvement in NNVM will try to solve these problems.</p>

<h1 id="show-me-the-code">Show me the code</h1>
<ul>
  <li><a href="https://github.com/merrymercy/tvm-mali">End-to-End benchmark</a></li>
  <li><a href="https://github.com/dmlc/tvm/tree/master/topi/python/topi/mali">Convolution and Depthwise Convolution Schedule</a></li>
</ul>

<h1 id="bio--acknowledgement">Bio &amp; Acknowledgement</h1>
<p><a href="https://lmzheng.net">Lianmin Zheng</a> is an undergraduate
student at SJTU Apex lab.  He is interested in machine learning
and building computer system.</p>

<p>The author has many thanks to
<a href="https://homes.cs.washington.edu/~tqchen/">Tianqi Chen</a> for his helpful
advice and <a href="https://github.com/yzhliu">Yizhi Liu</a> for his earlier work.</p>

<h1 id="reference">Reference</h1>
<p>[1] <a href="https://developer.arm.com/docs/100614/0302">ARM Mali GPU OpenCL Developer Guide</a><br />
[2] <a href="https://developer.arm.com/">ARM Developer</a></p>]]></content><author><name>Lianmin Zheng</name></author><category term="tvm" /><summary type="html"><![CDATA[With the great success of deep learning, the demand for deploying deep neural networks to mobile devices is growing rapidly. Similar to what we do in desktop platforms, utilizing GPU in mobile devices can benefit both inference speed and energy efficiency. However, most existing deep learning frameworks do not support mobile GPU very well. The difficulty lies at the difference between mobile GPU architecture and desktop GPU architecture. It means special effort is required for optimizing on mobile GPU. The non-trivial extra work eventually results in the poor support of mobile GPU in most deep learning frameworks.]]></summary></entry></feed>