机器学习编译(MLC)- Introdction
# lecture1 机器学习编译(machine learning compliation, MLC)- 概述
最近,陈天奇大开设了一门机器学习编译的新课,正好我也对这个领域很感兴趣,想跟着大佬好好学习,然后每一章节写一篇对应的博客。
课程主页:MLC | 主页
一. MLC 产生的背景:
整个机器学习编译产生的大背景就是:
-
机器学习的应用很多(CV,NLP, Speech),其中很多应用所用到的基本架构都是不同的(CV用CNN,NLP用Transformer....)
-
机器学习部署的场景很多
- 推荐系统算法通常由大型公司部署在云平台上
- 自动驾驶部署在车辆上的专用计算设备
- 物联网传感器也在内置微型芯片中运行着简单的AI算法
-
那么如何将我们训练好的深度学习的模型部署到这些不同的环境就涉及到下面这些问题(即使同一环境也会存在下面这些问题):
- 硬件: Arm 或者 x86
- 操作系统
- 容器执行环境
- 运行时计算库 (Runtime Libraries)
- 所涉及的加速器类型(GPU、TPU...)

那么将智能应用从研发阶段部署到这些不同的生产环境就是一个很大的问题,需要相当多的人工干预。(即使对于我们最熟悉的环境(例如在 GPU 上),部署包含非标准算子的深度学习模型仍然需要大量的工程。)。机器学习编译就是为了解决这样的问题而产生的。
本课程将讨论如何把机器学习从开发、研究阶段,引入到生产环境。我们将讨论一系列促进机器学习算法落地部署的方法。由于机器学习和开源系统正在开发新技术,机器学习落地部署仍然是一个开放且活跃的领域。然而,我们开始看到一些共性问题和话题,最终成为本课程的主题。
二.什么是机器学习编译
机器学习编译 (machine learning compilation, MLC) 是指,将机器学习算法从开发阶段,通过变换和优化算法,使其变成部署状态。
- 开发形式:是指我们在开发机器学习模型时使用的形式。典型的开发形式包括用 PyTorch、TensorFlow 或 JAX 等通用框架编写的模型描述(我所理解的即为我们在快家中定义的模型的那部分代码,简单的说就是计算图),以及与之相关的权重(训练得到的模型的权重文件)。
- 部署形式: 是指执行机器学习应用程序所需的形式。它通常涉及机器学习模型的每个步骤的支撑代码、管理资源(例如内存)的控制器,以及与应用程序开发环境的接口(例如用于 android 应用程序的 java API)。

这里有几点需要说明:
- 可以通过人工来完成这个编译的过程,但是可能比较费时费力,MLC 就是想将这个过程尽可能的由程序代替人工编译,并对其中的内容进行优化。
- 部署形态的很多内容包含在了开发过程中,只不过是被我们的深度学习框架给隐藏了起来。
我们使用术语“编译 (compilation)”,因为可以将这个过程视为与传统编译器所做的非常相似的过程,即编译器将我们的应用程序采用开发形式,并将它们编译为可以部署的库。但是,机器学习编译在很多方面仍然不同于传统编译。
首先,这个过程不一定涉及代码生成。例如,部署形式可以是一组预定义的库函数,而 ML 编译仅将开发形式转换为对这些库的调用。其次,遇到的挑战和解决方案也大不相同。这就是为什么我们希望将机器学习编译作为一个不同于传统编译的独立课题来研究。当然,我们也会在机器学习编译中发现一些有用的传统编译概念。
机器学习编译通常有以下几个目标:
- 集成和最小化依赖:即将必要的元素组合在一起以用于部署应用程序。(例如我们使用 CNN 来检测猫,那么我们就不会使用框架中包含的 NLP 相关的程序)代码集成、最小化依赖项的能力能够减小应用的大小,并且可以使应用程序部署到的更多的环境。
- 利用硬件加速:每个部署环境都有自己的一套原生加速技术,并且其中许多是专门为机器学习开发的。机器学习编译的一个目标就是是利用硬件本身的特性进行加速。 我们可以通过构建调用原生加速库的部署代码或生成利用原生指令(如 TensorCore)的代码来做到这一点。
- 通用优化:有许多等效的方法可以运行相同的模型执行。 MLC 的通用优化形式是不同形式的优化,以最小化内存使用或提高执行效率的方式转换模型执行。(内存优化、计算图优化)
重要的是,机器学习编译不一定表示单一稳定的解决方案。事实上,随着硬件和模型数量的增长,许多机器学习编译实践涉及与来自不同背景的开发人员的合作。硬件开发人员需要支持他们最新的硬件加速,机器学习工程师需要实现额外的优化,而同时算法工程师也引入了新模型。
三. 机器学习编译的关键元素
我们以一个两层全连接的神经网络为例,来探讨机器学习编译的关键元素。
-
1)在这个特定的模型中,我们通过展平输入图像中的像素来获取向量 (Vector);
-
2)然后,我们应用线性变换,将输入图像投影到长度为 200 的向量上,并运行
ReLU激活函数。 -
3)最后,我们将其映射到长度为 10 的向量,向量的每个元素对应于图像属于该特定类别的可能性大小。
张量 (Tensor) 是执行中最重要的元素。张量是表示神经网络模型执行的输入、输出和中间结果的多维数组。
张量函数 (Tensor functions) 神经网络的“知识”被编码在权重和接受张量和输出张量的计算序列中。我们将这些计算称为张量函数。值得注意的是,张量函数不需要对应于神经网络计算的单个步骤。部分计算或整个端到端计算也可以看作张量函数。

我们有多种方法可以在特定环境中实现模型执行。 上面的例子展示了一个例子。 值得注意的是,有两个区别: 首先,第一个linear层和relu计算被折叠成一个 linear_relu 函数,这需要有一个特定的linear_relu的详细实现。 当然,现实世界的用例,linear_relu 可以通过各种代码优化技术来实现,其中一些技术在的后面的课程中会进行介绍。 机器学习编译的过程就是是将上图左侧的内容转换为右侧的过程。在不同的场景中,这个过程可以是手动完成的,也可以使用一些自动转换工具,或两者兼而有之。

如果使用一句话来描述机器学习编译的话呢我们可以说机器学习编译就是张量函数的变化。
如上图,我们把 linear 和 relu 融合为一个张量函数——linear_relu。这样做有什么意义呢?就是说我们使用每一个张量函数进行计算的时候都涉及到 IO,如果将多个张量函数融合为一个的话可以减少GPU IO的次数,从而提高执行效率。
上图右边为 linear_relu 具体实现(这里为了方便使用 python 实现) ,即我们融合了张量函数后要对对应的张量函数进行一个实现。
四. 抽象(Abstraction)和实现(Implementation)的基本思想
我们使用抽象 (Abstraction)来表示我们用来表示相同张量函数的方式。不同的抽象可能会指定一些细节,而忽略其他实现(Implementations)细节。例如,linear_relu 可以使用另一个不同的 for 循环来实现。
抽象和实现可能是所有计算机系统中最重要的关键字。抽象指定“做什么”,实现提供“如何”做。没有具体的界限。根据我们的看法,for 循环本身可以被视为一种抽象,因为它可以使用 python 解释器实现或编译为本地汇编代码。
MLC 实际上是在相同或不同抽象下转换和组装张量函数的过程。(即将一个张量函数转化为另外一个等价的张量函数,但是呢这个新的张量函数,包含更加高效的实现 )我们将研究张量函数的不同抽象类型,以及它们如何协同工作以解决机器学习部署中的挑战。
以上图为例:首先我们将 linear 和 relu 使用了融合的优化。然后我们通过更高效的实现(比如使用 libcudnn)使得计算效率有一定的提升。

浙公网安备 33010602011771号