计算图是深度学习中最基础也最重要的一种数据结构，在计算图的运行过程中，每个节点会依次执行具体的算子操作。此时，需要有一定的内存空间来存放算子的输入和输出。这篇文档分析了 NNVM 中计算图内存分配部分的具体处理过程。编译计算图编译计算图的过程由 GraphCompile 执行，该 pass 位于 nnvm/src/compiler/graph_compile.cc。作为在 build 的最后阶段被调用的 pass，它的处理流程是：处理所有可融合的算子节点，然后将其转化为融合后的节点，包括将融合后的算子编译成为 LoweredFunc；建立原图和融合后的计算图的映射关系，并且据此构建出新的计算图。这一过程中会对 assign 算子进行特殊处理：标记所有的 assign 节点，并且检测可以简化的 assign，将其特殊标记并转为空操作；将处理完成的计算图进一步 build 为 module，得到目标代码；执行内存分配，并且对 Placeholder、assign 节点的内存分配情况作出额外处理。在这篇文档将讨论 NNVM 的内存分配机制，所以我们将主要关注上述第二步和第四步。[...]
在 NNVM 执行计算图优化的过程中，会在计算图上运行多种不同的算法，来修改计算图本身的结构或者属性。这些算法在 NNVM 中被组织成了 pass 的形式，可以使用 API ApplyPass 进行调用。说起来，pass 这个概念在编译原理中是很常见的：在编译过程中，对输入的源代码或等价的中间表示执行一次遍历的过程，就叫做一个 pass。传统意义上，我们可以把编译器的各个阶段都实现成一个 pass，比如：先对源代码做词法分析，待全部完成后对所有 token 进行语法分析，得到所有 AST 后再进行语义分析等等……但是通常情况下，为了节省时间，编译器会将部分或者全部的流程合并在同一个 pass 里，例如：在词法分析的过程中，语法分析器“同时”工作，构建 AST；然后在当前 AST 构建完毕后，立即对其执行语义分析和 IR 生成。而 NNVM 中出现的 pass 则是狭义的 pass。熟悉 LLVM 的读者应该知道，LLVM 中有一个 PassManager 用于管理所有的 pass，而每一个 pass 又可以对 IR 进行一次独立的操作（优化等）。这一概念的应用使 LLVM 变得高度模块化，添加一种新的优化只需要实现一个新的 pass 即可，而不必大幅度改动 LLVM 本身的其他代码。NNVM 中 pass 的概念和 LLVM 中的基本一致。以上是对 pass 这一概念的简单介绍。接下来的这篇文章记录了 NNVM 在执行编译优化过程中用到的一些 pass，以及这些 pass 的具体作用。[...]
近期在计算所一直在研究 TVM，尤其是其中 NNVM 的部分。由于之前完全没接触过深度学习，也没有系统的了解过一个实际的编译器项目，所以这些工作初期会比较费力。之前全凭自己头铁，直接硬着头皮读源码 + debug；目前好了很多，至少可以理清楚 NNVM 这套东西的基本流程了。于是把这一阶段自己记录的一些文档稍作简单整理，放在 blog 上，供以后回顾（作为黑历史）。这篇文章将简单介绍 NNVM 读入一个已经训练好的 model 的具体流程，包括前端如何将其他深度学习框架的 model 转成 NNVM 的计算图表示，以及 NNVM 如何将计算图构建为目标平台的二进制，等等。写得特别乱，也没指望大家能看懂（逃 = =[...]