超万字实录详解如何打造“好用”的自动驾驶智能芯片算法工具链

文章正文
发布时间:2024-08-12 03:38

大家好,我是地平线工具链产品规划负责人秦畅。今天我将会给大家介绍《如何打造“好用”的自动驾驶智能芯片算法工具链》,并谈一下在实践过程中,地平线的经验和产生的思考。

分享将从四个方面展开。首先什么是算法工具链;我们需要去解决的关键问题;地平线在解决这些关键问题的过程中做的一些实践。最后,谈一下我个人对未来工具链需要进一步怎样做的一些思考。

特斯拉国内售价三连涨,Model 3和Y分别再涨1500/2500元

01 什么是算法工具链

首先谈到智能芯片,它是如何体现智能的呢?芯片作为一个硬件电路实现的大集成,本身是不会有智能的概念出现。谈到智能芯片的智能性,其实是来自于智能神经网络算法的加持。

特斯拉国内售价三连涨,Model 3和Y分别再涨1500/2500元

在slide的左上,是一个神经网络里面非常经典的感知机模型,由一系列的神经元通过确定的规则排布得到了一个网络结构。这个网络结构将会建立起输入层与输出层的映射关系,通过喂入批量标记好的输入和 Label之后,经过学习可以对未来的、未知的、同类型的输入,达到预测输出的效果。拆到单个神经元来看,内部所含的计算是很简单的。比如我们对每一个输入乘以权重系数,再对其进行累加,这个过程也就是矩阵乘法里面的细颗粒的过程。

同样的,如果完全是这样的计算堆叠,我们就会看到它在数学上会等效于大量的矩阵连乘(连乘在数学上等价于某一个矩阵乘法的效果)。如果仅仅是这样,就失去了对复杂函数的表达能力。所以一般会在神经元后面加一些非线性激活函数,像引入ReLU、Sigmoid这些函数。学习的过程就是通过喂批量输入的样本和标记好的Label,去学权重设置,使得这一套权重能够尽可能很好地拟合这些输入和标记好的Label的映射关系。未来同类型的未知输入,就可以可靠地对需要的预测结果进行预测。

到智驾领域的算法应用,当然不再是感知机这样简单的应用了,当前我们仍然看到的还是CNN。虽然我们看到目前技术趋势上会有一些明显的变迁,但大部分应用中的方案仍是以CNN为主。比如Tesla在2020年左右披露的数据,在Tesla的一个方案里面,98%的计算量来自于卷积。那么卷积又会是一个什么样的计算?

左下角这张图中间是一个卷积的参数,我们叫它卷积核。左侧的是输入,右边是输出。卷积计算的过程中,卷积核对应到输入部分可视区域内的数据。我们将输入部分的数据与卷积核进行点积乘法,再做累加,这样就得到了输出的数据。通过卷积核在输入上的可视窗的滑动,遍历完所有输入的区域之后,就得到了完整的输出,整个过程类似右边动态图的效果。这个过程同样具有大量的矩阵计算特点。所以我们现在通过这两点可以看到,在卷积神经网络的计算里面有一个非常鲜明的特点,涉及到大量的基础计算,以乘加为主,量会特别大。

现在我们看到了一个明显的趋势就是未来Transformer的方案升级。Transformer确实给计算架构带来了一些鲜明的挑战,但是从刚刚分析的计算特点的角度来说,至少在智驾领域上的应用,主要的计算量贡献还是来自于矩阵乘计算和全连接计算,尽管对整体的体系结构带来了很大的挑战,但是计算的特点没有明显的变化。比如一个98%的矩阵乘的贡献会落到90~95%之间,其他的引入了更多激活计算,比如LayerNorm、Softmax用的更重。当然,对数据的形变、 layout管理方面,也会带来更高的挑战。

特斯拉国内售价三连涨,Model 3和Y分别再涨1500/2500元

那么,对于智能芯片来说,它解决的问题就会变成如何加速神经网络的计算。这一页左上角是一个非常简单的计算模型的示意。我们在计算过程中从memory里面拿数据,放到计算单元里执行,再把数据回写。在这样的模型里面,怎么样更高效地做神经网络计算呢?我们会自然地想到,在让整个模型保持现状的情况下,使它算得更快、频率更高,但提升频率是非常有限的。我们可以看到计算器件CPU在近几年频率上是没有非常大幅度提升的。但是对于我们所需要承载的神经网络计算量而言,如果要提速,那不是一两倍的概念,而是一个数量级的概念,所以单纯的提速是不可行的。

那么另外一条路是什么呢?我们看看左下的计算模型。

特斯拉国内售价三连涨,Model 3和Y分别再涨1500/2500元

我会铺更多的计算单元,这里放到了6个。这个计算模型一次获取6个数据,在一个计算周期之内,6个计算单元同时工作,它再去回写的时候就回写6个数据。在一个计算模型里面,我们不考虑额外读写上的压力,简单去考虑,会带来6倍的性能提升。这就是我们的神经网络加速器,或者说我们的智能化芯片去做加速的核心idea,就是去铺更多的计算单元。

当然铺的话也是有讲究的。右边是2017年谷歌发布的TPU-V1的架构。这是一个非常经典的实验。很多的行业同仁会拿它来举例子。我们可以看到在黄色部分是它所提供的计算单元的情况,这个地方的面积基本上也体现出了配重的占比,以大型的矩阵乘单元为最主要的核心,同时会辅以一些激活单元,刚刚也提到在神经网络里面会加一些非线性激活函数去处理这些任务。同时,一个网络不会这么的纯粹,只有激活加一些矩阵乘,同时像Pooling、Normalize这些操作会加上一个特定的单元,一般会称呼为DSU。它的作用非常明确是用来做什么的,而且可能是一系列不好在其他部件去实现的能力集合。

同时,为了支撑这套系统去运转,可以看到蓝色部分相关的内容,它放置了一个支撑存储数据管理的相关系统,维护适合于这一套架构的SRAM缓存,再去管理和DDR数据交互。在这个过程中,红色部分会涉及到一些驱动整个异构系统运作的必要控制单元。这仍然是一个非常经典的设计,现在各家的架构多多少少在这里面都能看到一些影子。当然现在会有所变化,比如会考虑在BPU加速核里引入一些标量单元,去处理未来神经网络里可能面临的一些分支计算,或者做核内调度。

在向量计算方面,如果需要考虑处理的特别计算越来越多,如果还是一个DSU的概念,那么DSU会越来越复杂。它承载的具体执行的计算类别会越来越多,所以也会出现一些替换。用一个更加强悍、灵活的可编程单元去承载一个任务,不仅仅可以去承载一些已经预见的、其他部件不好去做的事情,同时提供给未来开发者一个完全开放、具有可编程性,能够去做未来可能产生的新的计算类型。

那么对于我们工具链来说,恰好夹在中间了。我们会看到这样的情况,往上看,面向各种算法Deep Learning开发的框架,算法开发者基于这些框架得到各式的模型,这是输入;往下看,在地平线的工具链打造里面,面向地平线征程系列的平台,包括我们已经量产的征程2、征程3和征程5,以及未来将会采用在今年车展上发布的BPU纳什架构的下一代,应该是征程6系列。

我们要在他们之间去建立一个桥梁,使得算法能够在这些芯片上跑起来。

02 自动驾驶智能芯片算法工具链解决的关键问题

特斯拉国内售价三连涨,Model 3和Y分别再涨1500/2500元

跑起来是前提,跑起来之后桥梁是否稳定有两个核心要素:

第一个是我的算法部署在你的芯片上是否足够准确。没有准确为前提,所有的高效也都是免谈。所以“准确”我放在前面了。

第二个就是它的效率是否足够高。这将会成为算法工具链所需要解决的两个关键的问题。

在现在算法工具链语境下,我讲的准确性问题不是像算法训练一样,模型是否能够有很好的预测能力,是否训得足够好,它并不是这样的准确性问题。准确性的问题来自于硬件的选择。刚刚提到智能化的芯片加速神经网络模型,会堆叠更多的计算单元,堆叠也就是意味着更高的成本。成本会体现在两个方面:

第一个是需要更多的面积去承载这些计算单元;另一个则是更多的功耗。一次推一批计算的时候,需要的功耗也是一个很重要的因素。

我们做出来的芯片毕竟是面向商业化的场景,需要综合考虑成本及使用功耗的问题,所以在堆叠这些计算单元的同时,我们也想到它必然会增加成本,同时去考虑如何减少这样的成本。

这里面有一个重要的方向是数值精度上的选择。这一页贴了两张图,这个图是一个加减运算器的实现,整数和浮点数的对比。我们要看到的一点是,浮点数的电路实现的复杂度明显高于整型的硬件实现的。核心原因来自于二进制的浮点数表示和整数的形式很不一样,原因在这里不深究。

特斯拉国内售价三连涨,Model 3和Y分别再涨1500/2500元

下面这张图是我比较喜欢用的一列数,现在可能看起来比较老了。虽然是2014年的数据,但里面还可以看到一个明显的特点:中间偏左的部分是它能效的代价,右边是面积的代价。我们以加法为例,做加法的时候用8b的加法实现,和16b的半精度和 float32去比较可以看到,随着计算精度的提升,能耗面积的代价都在增长,所以这就成为了一个很好的选择。我们在铺大量的算力单元时会充分考虑使用 Int8的计算,当然也会做出一些不同的选择。我认为完全的Int8可能用起来会比较困难,会适当的去考虑比如在一些向量激活的计算上引入浮点,或在矩阵的计算上适当的保留一些浮点。比如Int16是不是能够保留?所以各家去看芯片体现它有多少个cost时,一般都会指明它支持什么样的计算精度。当然,如果加速的平台没有与训练的平台做到完全一致的浮点计算,它就会带来一个准确性的挑战。训模型的时候,在这个算法的开发环境下做算法验证,它就是一个完全的float32甚至更高的验证方式,你在芯片上改变了计算精度,你肯定对我的计算部署准确性是有一定的影响的。

到工具链的层面,我们要去做好量化的过程,使得我们虽然要去配合硬件做具体计算单元的计算精度降级,但整体模型的预测效果不能有太明显的损失。比如在智驾行业,基本上也会有一些共识:我们在驾驶相关的模型部署上,接受一些压缩的方法去换取更高的性能,但精度的偏差不能太大。比如较于我的浮点的验证环境下作为base,它的相对的精度损失一般会控制在1%以内。如何去做好这个事情呢?这就牵扯到神经网络,神经网络是支持我们去做这个事情的,涉及到模型的量化压缩的一个技术方向。这个事情怎么解释呢?一个神经网络里面每一层都在做计算,每一层都会输出一些结果,我们把这些中间结果叫做特征。

特斯拉国内售价三连涨,Model 3和Y分别再涨1500/2500元

怎么去理解特征呢?就是一个常识性理解,一些研究对中间产出的特征做可视化。比如在一个人脸的任务里面,一些浅层的特征里面可视化之后可以看到一些前景、背景的区分,一些边缘的特征出现;在中层的特征上,我们看到了人眼、人脸、眉毛这样一些中层特征;到偏高层的时候开始见到整个人脸,再在这个基础上做预测。这是可视化的效果,但计算机二进制数值的表示是数据的分布效果。比如更常用、更容易理解的是在计算机屏幕上大家今天看的直播活动,看到了很多画面,画面每一页代表了各式各样不同的信息。整体来说,它在计算层面上就是各种 RGB数值的组合,这就是数值的分布带来的效果。

数值分布会留下了一些可压缩的空间,这一页左下角的部分是谈到量化压缩时候经常会引用的一张非常经典的图。上面是压缩之前在浮点数据分布的空间之内,我们去建立的一个到Int的等比映射,相应的数据都等比例落下来。这样我们数据的表达范围变少了,但可以看到里面由于 Int的表达能力,比如不映射过来的时候能到111.5,取一个round函数的方法,四舍五入映射到整型数据上会有一些偏差。但是整体来说,它的分布不会有太明显的变化,当然前提是量化做得好。

还有一种数值的分布改变,压缩有一种更通俗但不是很严谨的理解。拿一个像素的方式去举例:我找了一张猫的图片,原图在左边,它的像素会偏高一点;右边对它进行了一些处理,把它的像素值减少了。虽然我们可以明显看到里面是有一些损失存在的,没那么清晰了,但其实不影响我们去理解它。两个对比之下,不影响我去理解它是同一只猫。这就是我所理解量化的、比较好传递的核心认知。

在工具链具体的实施上,我们真正把这个想法落到工具的实现里面去。刚刚讲到的量化会很简单,在这种情况下可以直接做一个映射,但适用性就会非常有限的。如果把量化直接暴力解释为映射,各种数据分布就会给我们带来挑战。

特斯拉国内售价三连涨,Model 3和Y分别再涨1500/2500元

在原始的数字分布中间,很明显会是一些有效的数据在两端噪声比较多。如果完全根据它的最大值、最小值的区间,映射到 Int8的表示范围内真正有效的数据去表示的时候,用到 Int8表达能力就会很少。这就会严重导致数据相对分布的表达失真。对应到刚刚那张猫的图片,像素损失太严重了,我不能够再认识出来它是一只猫了,更别说是同一只猫。

为了做好这件事,工具链常见两条路径,一个是后量化的路径,另一个是量化训练的路径。后量化的“后”侧重讲的是训练后的概念,我使用一定数量的样本,用训练好的模型去推理,这样就会拿到一批用真实数据去推并产出各层的数据分布。大概像左边这张图,能够看到普遍都具有这个特点:中间的分布非常密集,两端散得非常开且数据非常稀疏。我们可以先做一个假设性的认定,数值分布应该在中间取,规则可以在简单示意图上做非常简单的划分,把有效的空间截取出来之后,其他的直接丢弃,只在有效的数据范围内做映射。这样就能尽量有效地还原数值的分布,把量化做好。

为什么说是尽量呢?有一些后量化理论上也不能确保我们解决所有问题。我们在用后量化去解决问题的时候,会提到一个概念叫“量化不友好”,但是这个概念我尝试去做后量化的时候,可能会发现这样一种情况:呈现数值分布是集中的,但是集中不在一个点上,分布在左右两头,中间比较稀疏的分布特点。这样去找范围再去映射,就很难真正有效地应用到 Int8的表达能力,因为中间原始数据会有一段空档。为了解决这个问题,确保量化方式的稳定性,对各种情况都能处理,就会提到“量化训练”的概念。我们把量化的行为追加到训练的过程中的好处是:发现了量化不友好的问题之后,可以调整权重分布,使它达到所谓的“量化友好”的状态。因为在训练过程中,权重本来就是在调整的过程中的,训到一定程度之后,又可以开始做前面的统计、映射这些过程了。核心就是权重可变,解决问题的潜力也就越来越高了。

当然还有一些其他的解决办法,比如混合精度。例如在做后量化的时候只是部分分类的数据呈现这样的特点时,量化不好做,那干脆就不要做了,我在这一层直接用浮点的计算。还有一些是只要分布是典型的,就可以在量化的方式上选择更多样的方式。我之前认为量化数据在一个集中点,如果数据分布在两个集中点,那么也可以推出相应的量化方式。解决问题的方式是多样的,目前后量化加混合精度进行量化训练会是一个使用比较广泛的方式。

下面是第二个关键问题:高效性。

我们的工具链要去把上级算法框架得到的模型在芯片上部署起来,使它能够跑起来。所以,最简单的原始idea可以怎么样去做呢?

在计算图里的每一层计算是确定的,然后将计算拆解出来,映射到相应的计算部件上去。它们的执行次序也是确定的,然后按照执行次序,该加载数据的时候加载数据,该算 Conv或者算Matmul的时候就去上Matmul,该Pooling的时候就Pooling,非常保守地一步一步去做,就会得到下面示意的时序图。

特斯拉国内售价三连涨,Model 3和Y分别再涨1500/2500元

这个图其实是一个非常不理想的部署结果,可以看到计算资源没有被充分的利用。所以,高效性的问题对于编译层面来说,是如何把计算的资源充分地调动起来。

地平线做的应该也是行业内普遍会做的事情,从两个方面提高它的性能:第一个是去提升数据的复用度,第二个是提升并行度。

特斯拉国内售价三连涨,Model 3和Y分别再涨1500/2500元

数据复用和效率的关系怎么理解呢?我们刚刚看到 GPU的计算模型里面,核内会有一套缓存管理系统,同时也负责和DDR的交互。这样我们就可以看到两层的存储的概念。第一层是核内的缓存系统,比如SRAM;第二层就是核外的动态存储DDR。越靠近计算部件的缓存器的性能会更高,如果能把数据停留在缓存里面,充分地复用减少跟 DDR的交互,整体来说性能会更高。从时序图上来看,我就会尽可能地避免计算等数据出现这样的情况。

那复用怎么做呢?首先会提到两个概念,地平线是计算的拆分和融合。拆分是一个非常常见的做法,比如还以GPU为例,Conv计算的时候把两份输入,一份Feature的输入,另一份ViT输入,按照确定的规则展开之后,变成一个非常大的矩阵乘。这种方法在一定程度上是用一定的存储空间去换得计算效率,或者使计算得以实现。但从拿到的结果上会看到是一个非常大的矩阵乘,在计算的加速核心里面所提供的一个矩阵乘单元的力度不会那么大,就是几十乘几十,撑死了100×100。至少我还没见过100×100这样大的规模,做到这个程度已经不得了了。但矩阵乘法的规格肯定不止这样,所以这个过程中就必须要拆分。

在地平线的实现中,比如在Conv上面,有一点特别的是我们不会把它展开,而是直接拆 Conv,这样也方便我们去做融合,提升数据的复用。怎么理解呢?在这样的计算示意里假设还是卷积计算,第一层做完部分计算之后,同时也在准备第二层的输入。那么如果是一层层 layer by layer的计算,每一层输入的计算结果是非常大的,特别是在智驾的场景下图像的输入分辨率普遍都不低,产出的中间Feature每一个layer都会非常大。比如几兆的缓存空间是不够的,不足以成像的,等第一层算完,必定第一层的过程中就要回写DDR。

在地平线的策略里,我们采用先拆完之后再去做融合的策略。这指的是第一层做完部分计算,第二层的部分已经输入准备好了,这时候会优先跳到计算下一层部分。依次这样递推下去,找到一个合适的位置停下,后面发现这个策略不能再走了,又回到前面,继续做这样的计算。相对于layer by layer,我们能够很大程度上避免DDR的回写。实际上拿到地平线的芯片去评估的时候,大家往往会考虑两个要素:第一个是它的性能结构怎么样?第二个是它的带宽情况怎么样?

首先我们看单帧读写。同样的模型在地平线的芯片平台上,单帧读写所需读写的数据量应该是明显偏少的状态,这是一个非常好的结果。这样在一个确定好设计帧率的系统上去运行,需要用到的带宽资源就更少,这样就给应用腾挪出了更多的资源。

其次是指令并行方面,指令并行也就是编译器,基本上是各家都要充分去做的一个事情。在计算的过程中要充分的Pipeline起来,每一个计算单元各司其职,最典型的就是数据的加载、存储单元和计算单元并行。我们要尽量避免计算单元闲置,数据搬运的单元一直在工作,也就是计算等数据。这是一个指令并行的优化,基本上每家都会做这个事情。

是不是有了这些之后就足够了呢,把数据的复用做得很好,想到了办法在并行度上也充分地优化,是不是就能够解决所有的问题呢?答案肯定是否定的,现实的算法结构的多样性往往可以给我们带来各种各样的挑战。

特斯拉国内售价三连涨,Model 3和Y分别再涨1500/2500元

举一个有点老的例子,但也是一个非常典型的例子。在最后一层的计算中,我们甚至需要用到第一层的计算结果,这对于数据复用来说是一个非常大的挑战。在持续推理的过程中,最理想的情况是算完即弃用。这种网络结构不要求把数据不断地留下来给后面用。这种网络使得推理芯片比做训练数据的芯片更简单,因为不需要缓存每一层的结果,相对来说是算完即弃用,拿着新的数据再继续往下去算。如果它失去了这个特点之后会变得更困难,这也是业内普遍会认识到这样的一个问题。这也导致了大家会非常关心一个结果,就是有多大的SRAM。在刚刚提到的用缓存策略去加速计算,避免计算瓶颈的运作模式里面,理论上有更大的SRAM潜力是越高的。

另一个方面还是会看到SRAM不可能无限大。相对来说,它集成到芯片里面的成本远比DDR要明显高很多。所以一般都是有限地集成多少个兆,在边缘侧的推理芯片里面,单核放十几兆已经非常大了。有限的空间必然放不下所有的东西,所以数据回写还是不可避免的。回写又关注到带宽,数据的搬运能力怎么样?存不下,就使得它的数据搬运和DDR交互来的更快,这些是由带宽决定的。比如在公路运输的时候,把4车道拓成12车道;铁路运输的情况下,把绿皮换高铁,这都是为了更高的运力。理论上也是没错的,但整个模型性能的最终确认是一个复杂的系统过程,这些基础的因素确实能够保障得到更好的性能。但其实各家都有一个共同的目标,就是达到更好的性能,但里面会做出不同的权衡,因为并不是提升之后,对你关注的所有网络都是有帮助的。比如提供一个更高的SRAM,但未来所需要的应用里面极有可能是卡计算的,更多SRAM其实对性能没有非常明显的帮助。理论上会好一点,好5%,反正不是非常亮眼的这样的存在,但SRAM提升的代价是实实在在的。

所以我们认为总的来说这些都是中间的过程或者因素,真正要去看我们未来所要使用的计算平台性能如何,最好还是用实际的物理芯片、计算平台和开发板,拿到它相应的工具链去实测未来要用到的这些模型在芯片上的性能是怎么样的?当然有时候样本少看到的结果是非常不一样,有时会看到的结果非常乐观,有一些非常悲观,所以理论上还是尽量去看更多的模型,给芯片一个客观的评价。

03 结合实践做“好用”的算法工具链

涉及到在芯片上真正跑这个模型,就会用到算法工具链提供的一整套开发套件。目前展示的这一页就是地平线的算法工具链构成,大概分三个部分:

特斯拉国内售价三连涨,Model 3和Y分别再涨1500/2500元

第一部分是训练侧相关的,偏向模型训练端和模型前端,框架去对接的这个部分。我们会对接主流的深度学习框架,同时提供两种方式:

第一种是刚刚提到的后量化的方式,在大部分早期接触里面,我们的客户都倾向于去选择这种方式。因为这种方式会有一个很好的特点是它和训练是解耦开的,交接面是非常清晰的,操作起来也非常简单,评估会非常快捷。

另外一套就是刚刚提到的量化训练,我们在 Deep Learning的框架里面提供一些插件,在浮点阶段之后添加量化训练的过程,可以提供更稳定的精度保障。

第二部分是在量化问题解决完之后的一些编译优化性能保障的套件。

我们提供的工具范围包括模型的检查器、模型的编译器、一些仿真推理性能的调试器等等。如果预期顺利可以通过这一套得到一个上板部署的模型。

第三部分,算法工具链同时要基于前面这些规则得到的模型,在嵌入式端去提供一套部署预测库。它在模型的部署层面会偏简单。我们会提供一个必要的接口能力,包括模型的加载、输入数据的准备、一些推理的接口,当然也包含一些必要的模型调度相关的基础能力。

这就是大家能够接触到的地面线当前最新一代J5的平台展现出来的面貌。地平线肯定也不是一开始就做到了这样的形态,这个过程中其实还是有一些故事的,我们是逐步演变到了这样的状态。

特斯拉国内售价三连涨,Model 3和Y分别再涨1500/2500元

地平线是在2017年开始做真正意义上支持生产的一套工具链,以相对独立的产品的存在形式去做。当时我们选择的是 MXNet,在刚刚提到的偏前端的模型压缩的方式上,我们直接选择了QAT路线。当时有一个判断,认为在智驾的场景下,因为是和安全相关,我们要尽一切可能去提供更高的准确度,而QAT相对来说的精度保障的稳定性更好。

当时也是自己做的,为什么呢?因为2017年做量化训练还是比较早的状态,至少在当时时间点,市面上是没有更好可以去选择的基础的。最早的可公开地获得的一些量化训练能力的框架,应该是2018年谷歌推出来的。其实在2017年的时候,我们地平线就已经在做这样的事情,飞快地把它推到我们早期芯片里面,去支撑智驾的相关应用落地。这一套工具链也在未来的一段时间内在用,比如J2以及征程3的早期,都是用这一套框架支持它们的量产,带来了很大的出货量。

但是,往外推的时候就会有一些问题。

首先是地平线自己用好了,其实我自己也体验过。当时我也参与了一些算法和嵌入式相关的中间模型部署优化的相关工作。从嵌入式的角度去接触MXNet之后,它的体感还是特别好的,而且和后面又使用的TensorFlow相比,我认为还是一个非常好的体验。但是很遗憾由于一些生态问题,工具本身好不好用是一方面,它的生态基础是不是很好也是一个很重要的影响。我们会发现真正市面上用的时候,潜在伙伴并不是那么多,相对来说TensorFlow当时在工业界关注度更高。因此在2019年的时候,我们又推出了一套在TensorFlow框架上去做的工具链。当时同样还有一些选择,现阶段回头来看不是那么完美。当时仍然选择了走QAT路线,还是坚定不移地认为高精度保障肯定是第一要务。

再就是我们定义了一套算子集。一开始踩到了一个很大的坑,我们认为整个算法的搭建,从原始的idea开始就应该充分考虑未来怎么样去用。比如在最前端和中间步骤有一些不契合的地方,在业务上大家应该就讨论避免掉。因此,定制了一套算子集,这样达成了一个什么样的效果呢?用地平线我们所推出的算子集去重新构建网络,从零开始训出来,得到一个比较好的训练效果。这样在图的优化层面,要做的工作就会比较少。比如典型Conv+BN+Relu的融合,我可以直接提供CBR这样的组合算子,再就是给一些未来可能需要在计算图里面去折叠、融合的算子,就直接提供一个大算子重新去搭建。

这样做的效率肯定更高。但实际上引入了一个问题,它对于我们的客户已有产出的延续,是一个非常不友好的状态,意味着用它需要从零去搭建,那以前的积累怎么办呢?这是一个现实的挑战。

另外,它对于整个算法开发过程侵入性非常强。虽然算法设计的思路仍然会得以延续,但是你把整个算法开发过程都推翻了重来。用这一套工具去做的过程中,它会把整个算法训练过程中产生的问题,都引到工具链或者支持的问题中来。

比如我们自己在用地平线的平台之前,要去在边缘平台或无关的算法开发环境下去训一个模型出来,可能本身很多问题就会发生在这个阶段,比如发现训不上。但如果一开始直接从那个阶段去切入,这些问题就都会落到工具链层面上来,这个是很难分清的。在早期可能花很多时间去训,但模型的效果精度不及预期,会发现是模型训练本身的问题,还没有到需要用工具链去解决的问题上来,也不是我们的算子出现了什么问题。但是当我们一旦提供这些工具的时候,我们的伙伴会很自然地优先倾向于怀疑算子是不是有问题,你的训练框架是不是出了什么问题,所以支持的投入会偏大。

一个核心的原因是跟用户的交接面不是那么清楚,它的支持会非常复杂,甚至会把我们的研发团队也卷入到一些支持工作中,这是一个非常不健康的状态。

特斯拉国内售价三连涨,Model 3和Y分别再涨1500/2500元

然后到2020年,我们迎来了一些变化,当时推出了征程3。对地平线几代架构有了解的同学,应该有了解过架构的名称,特别是用过工具链的。征程2这一代BPU架构叫伯努利,在征程3上我们叫伯努利 V2。

它在BPU架构上没有一个非常大幅度的变化,仅仅迎来了微小的架构调整。从工具链层面看到一个非常鲜明的特点是什么呢?它在支持量化的方式上,从J2所支持的移位量化的方式,转成了支持乘scale系数的方式。这也使得我们具备了做后量化工具链的条件。

在这个时间点上,我们开始推出了一个在征程3和旭日3上启动的浮点模型转换的工具链。通过各个框架得到模型,把这个离线模型导出来,再放到整个集成工具里面去做校准、编译,得到一个上板的模型。这个阶段好处就出来了,我们之前踩过的坑基本上也都填上了。对于客户的已有模型的复用做的比较好,整个交接面也是清晰的。如果导出来的模型本身就有一些准确性的问题,问题可能不是我们做优化过程中产生的问题,交接面是非常清晰的。所以在这一段时间,用户的体验上反馈比较好,使用量也有一些增长。

差不多在同一时期之内,深度学习框架又迎来了一次变化。当然这个也不是那么突然的,自然而然就到了这样的结果。PyTorch的重要性越来越高,当时在用户群体里面去支持PyTorch框架的呼声越来越高。那么,我们进一步就在PyTorch的环境下加入了一套量化训练的插件。提出的这个插件沿用至今,也是量化训练里面最主要使用的一个插件。

在做这一套插件的时候也是吸收了一些之前的经验,尽量还是不能省的时候不省,保持了和原始的浮点模型对接的阶段,不要侵入到原始的浮点模型训练阶段,而是在它的基础上追加量化训练。之前浮点的训练阶段产出是量化训练阶段的输入,我们会去负责PyTorch里面的一些必要算子替换,比如add。为了做量化训练,我要去做特质化,使它能够加上量化节点,在学习的过程中训练量化参数类似的事情。这一阶段,即使在量化训练阶段,我们也开始和浮点模型训练有了比较清晰的边界。

做到刚刚这样的程度之后,基本上也就形成了前面给大家秀出来的我们工具链到目前为止所呈现的过程。我们的用户群体也在这个时候开始快速增长,不管是我们在商业上一些To_B的合作伙伴,还是开发者群体里面涌入了很多的用户。但这也带来了一个很大的挑战,就是算法的多样性。我们看到各式各样的算法涌进来了,可能就会频繁地出现了做不好的情况,里面的问题有很多。

特斯拉国内售价三连涨,Model 3和Y分别再涨1500/2500元

典型的、首要的问题就是如何让多样的模型高性能实现?常用的计算是可以去枚举的,但在真正计算优化的时候,为了做到更高的性能,就会给计算的图上做一些优化。比如一些能够折叠的算子去做折叠,小颗粒度的算子去做融合,如果得到一个数学上等价的实现,我们要把它识别出来,使它能够做一个更大颗粒度的高效的计算,映射到已经充分优化好的实现上去。所以我们就会根据输入去做大量计算、融合范式的补充,很多的计算图层面的优化。对于编辑器层面上来说,不断引入case也可以看到一些优化的方向,直白点说会意外地发现一些硬件上的优化技巧,编译器层面会产生出一些新数据的拆分方式,在并行优化的方式上也能够做出一些新的策略。

第二个维度是模型硬件友好度的问题。软硬结合是地平线已经深深贴上去的理念。我们认为一个好的网络,真正在目标计算平台发挥出极限性能的时候,一定要充分地契合硬件上的特性,使性能充分发挥。即使你当前跑的挺好挺满意的,但如果和硬件的契合度够高,它可以有更好的表现。这也就是我们说的模型硬件友好度的问题。模型是否硬件友好,其实很大程度上已经决定了它优化的天花板。

地平线从J2开始就有一个非常重要的、一直延续至今的特点,就是对于Depthwise、Group Conv这些能明显减少计算量的 Conv计算有倾向性地支持,这就是我们做软硬结合的时候认为未来哪一些东西是重要的。其实神经网络从非常早期提出到很长的一段时间之内,我们做芯片的时候去看计算模式,是没有发生非常明显变化的。到2016年,Xception推出的时候带来Depthwise,我们当时认为这是一个非常重要的计算。它很大程度上减少了计算量,但对于准确性不会有非常明显的影响,所以认为这是一个非常重要的计算。因为计算这种东西是一个硬性的东西,这个层面如果一开始起点很高,需要的计算压力负载也高,优化空间的极限也和计算量紧密相关。

我们认为Depthwise、Group Conv这些能明显减少计算量的操作是一个非常重要的方向,所以当时在芯片里加入了特别优化支持。但是出现了一个很严重的问题是它的结构对于GPU的训练环境极度不友好,可以在部署的时候做到高效,甚至在一个智能手机的终端设备上做得非常高效,但它对于GPU训练平台是资源不友好的。我们早期也会有一些同学去训这些模型的时候发现,在整个集群的计算资源监控平台上,资源利用的情况会特别低,这也是一个很现实的问题,也是导致没有非常大面积开发结果的原因之一。我们在很长的时间之内擅长做这样的事情,拿着MobileNet、GoogLeNet向我们的客户推荐,但也仅限于此,很长的时间内没有更多相关的学术界或产业界的持续补充。好在坚持一段时间之后,有两个方面的变化:

第一个方面是外部条件带来的利好,学术界、工业界涌现出来更多的最新实践。先后像EfficientNet、EfficientNet-lite、EfficientDet这些系列的推出之后,在旭日、征程早期的平台上用到了这些模型,使得我们也有一些合作伙伴得到了高效地部署实现。这也是最近相对来说会新一点的 ConvNext,ConvNext还是Depthwise。但在Depthwise计算上,当把卷集核放大的时候,相比较Normal Conv来说,ConvNext对计算量的增长影响还好,而且它在计算预测的准确度、训练环境的资源的友好度方面都有提升。所以幸好行业内对这个方向有持续的关注和产出,我们可以直接复用。

另外一方面,也是看到这个问题之后认为我们不能止步于此,不能仅仅依赖行业内自然而然得到一些有帮助的成果,我们自己也应该主动的去做更多的事情。从工具链的团队来说,我们开始不局限于做工具本身,会在算法层面上更加关注如何做一个高效的模型。因为用户群体本身对我们也是有诉求的,当算法的模型不够高效时,在用到地平线芯片、工具链时候,自然而然会在工具链的支持团队里寻求一些建议如何去把它做的高效。为了更好地做支撑工作,也为了更好地提供素材,我们在做工具的同时也在做高效模型的探索验证工作

最早期我们在经典的视觉任务上推出了高效的BPU友好的视觉分类任务、检测任务、语义分割任务。在2022年下半年至今,我们推出了基于自动驾驶场景任务拆分出的系列技术点,然后推出了大量的具体技术点的参考算法,以及在做这些参考算法的过程中,做的一些具体的优化。针对为什么要去做优化,提出了大量的最佳实践,当然这些最佳实践都是 免费提供在我们工具链交付包里面去的。这些很大程度上给我们的合作伙伴提供了很好的输入,如何去做好硬件友好度地提升。

第三个方面是一些精度问题的处理。我们碰到的各种方面都会有个问题,尝试了各种修补策略,比如哪些需要高精度策略的补充,再补充更多校准的算法,KL之后要MAX,MAX之后要MIX,再就是是否启用perchannel等等。我们陆续把这些做齐之后,每项参数的出现都对应一个具体问题,过段时间就会发现真正去调的时候,它的参数量要配的参数是非常多的,后量化也像写代码一样要写非常多的参数。很多情况下你不用去动这些参数,所以有一段时间我们把它转成了默认值。但是过一段时间之后会发现设默认值是有倾向的,在设默认值的时候选哪个其实也要纠结,因为默认值的设置会代表配置的倾向。后面我们也转向了一些自动策略,比如这些参数是否应该开启,是什么样的配置,是一个自动搜索的过程。我们的研发应该在补充技术策略的同时,更多地考虑如何把过程做到自动化。不能随着策略越来越多,客户所需要加强的认知就越来越多,搞得每个人都是工具链的量化专家,我们要避免这样的情况出现。

还有一些非常零散的问题就是用户体验的问题。我们会有各种各样的反馈,比如早期文档的体验不好,文档没有讲清楚,文档是一个形式化的存单,你写的和你到时候要给我的东西不太一样,这些东西又和我得到的效果不太一样。在这个过程中,我们也经历了非常多的实践,也是在和用户的打磨之下,通过一条条具体问题的修复,带来了整体体验的提升。我们早期用J3的客户,过两年再用J5的时候会有一定地提升,因为是平台化的开发,所有的提升都一直积累在这个平台上。回到J5去看,经历两年的时间有了一个翻天覆地的变化,体验有了更明显地提升。在这种改进之后,产生了一个从全景来看的偏复杂的使用体系。在这个过程中,我也在思考怎么样去跟我们整个研发团队达到共识,应该有好的想法就是怎么样去把它做得好,里面的核心是什么?

特斯拉国内售价三连涨,Model 3和Y分别再涨1500/2500元

我提出来的核心是,在理想的情况下核心路径是尽量短的,步骤尽量少的,就像GCC,我最好不要出任何问题。如果没有任何问题出现的情况下,我自己编码编好, GCC基本上是没有存在感的状态,这样就可以自然而然把代码变成应用。我们认为算法工具链也会是类似的好的工具,我们追求是降低存在感。当然存在感非常强的时候可以使用地很频繁,但真正好用的时候,其实用户是感知不到它的存在的。

在现实中,现实与理想的差距总是存在的,我们有各种Debug旁路分支的出现。这里面的一个要素是什么呢?我们收到很多用户的反馈说出错了,但是他可能得到的结果是扩散的,或者是某一个 Python环境下的对账错误,一个CHS的Print结果也看不懂,很多应用性的问题就来自于这方面。所以我认为这个地方的一个指导原则是非常清晰的,总结为“知错能改”。“知错”是我们用户任何的反馈互动,要做到和用户的动作相关,他出错的时候知道是由于什么的操作导致了错误结果。相应的如果想要结果做得更好,他能够接受什么样的合法操作,这是一个知错能改的状态。当然我们也没有完全做到这样的状态,只能说是一直朝着这个方向去努力,这也是我个人的一些思考。

04 算法工具链的演进趋势

对于工具链,未来我们要去做一些什么样的事情呢?我们做工具链的几个核心能力其实是比较清晰的。我们要把计算图从算法的训练环境下导出来、做模型量化等压缩技术、性能优化。简单的说就是去干这些事,这些事情也是持续可干的。

特斯拉国内售价三连涨,Model 3和Y分别再涨1500/2500元

比如计算图的表达。我们最早期在FasterRCNN上,是典型的变成了两阶段的网络。在第一阶段之后,通过一些Proposal、ROIPooling这样的中间衔接操作,把第二阶段连接起来。在很多的芯片平台部署上,两阶段模型的部署是偏复杂的。在地平线现在的处理里面,我们会在公开的框架表达能力上追加一些我们的认为主流网络自定义算子,比如FasterRCNN,使得它能够得到一段计算图的表达,降低部署的难度。

现在到BEVFormer,同样又会有类似的问题出现。当然BEVFormer之后,我们认为这个问题持续在扩散。它其实是从单目感知做后融合,到BEV感知,也是端到端的演变阶段性。我们认为未来的感知任务将会升的更远,从一个记录的图像输入,不仅仅是一个向量空间,还有一些预测的动作都会包含到里面,整个计算图会越来越复杂。所以,计算图的表达仍然会是很大的挑战。所以,我们看到PyTorch第二代的升级里面,有一个很重要的部分就是计算图的表达。最近我也看到了一些边缘端的部署方向,我认为是契合的。

我们需要在这个趋势里面找到一个适合自己的方向,是不是去复用Torch,还是一些其他的选择,我认为是值得探索的。

然后就是量化压缩方面,混合的量化方式。我们未来应该去选择什么样的计算精度呢?现在看到的计算精度也是越来越多了,我刚刚反复以Int8、FP16为例,这个问题显得偏简单。目前业界实际关注到这个问题的解决办法也是各种各样的,出现了 Tf32、cFP8、FP8,还有更低的4bit等,计算精度的选择会越来越多。工具链就要做出选择,选什么样的计算精度。不能啥都要,因为啥都要的情况下,导致硬件没办法承受,不堪重负,做不出来。

性能优化方面也是同样,未来去承载一些什么样的计算?我们目前看到了整个的计算没有完全到收敛的状态,它在持续变化。当然这是一个好事,更多优秀成果的产出,使得做智驾的积累越来越厚,大家可以做的越来越好。但是,对于芯片工具链是一个现实的挑战。那么我们如何去应对未来的变数呢?我们在数据的计算类型上要做选择,在数据的存储系统的设计上要做选择,我要几级的存储缓存结构,要多大的带宽等等,这些是各种问题导致的。

同样在未来也会产生一条更受重视的要求,就是开发效率。我们持续在解决的过程,会发现这些技术的更迭是为了什么?这导致了一个结果,就是这个行业的发展变快。如果行业的发展变快,大家就需要非常强的应变能力,不仅是我们自己,还有我们的目标客户。他们需要在短期之内更快去OTA算法的效果,并且新的算法技术出来的时候,能够快速跟进。在缩短从原型到真正量产的周期过程中,我们能够做什么?这是开发效率的问题。很长时间内能做出来是好汉,现在基本上我们看到最近各种热点话题卷向大家谁做的好,在以数据驱动的感知任务里面,效率是一个非常关键的问题。

为了做好效率方面的事情,我们在当代可以持续地打磨,然后给下一代提一些具体要求。但涉及到刚刚提到的计算精度的选择,以及下一代硬件的计算架构应该是怎么样的?它其实来自于一个判断。这个判断就是未来我们需要一个什么样的计算。所以有很重要的一点,我们做产品化的工具链的同时,需要去构建一个与芯片联动的平台。

特斯拉国内售价三连涨,Model 3和Y分别再涨1500/2500元

我们服务于当代,也要服务于下一代。对于下一代来说,我们不断吸收这些问题,使它转化为下一代的Benchmark。在Benchmark的集合之上发现当代做的不是很好,那我有什么样的方法使它可以做得更好?比如我刚刚提到的特别的量化的方式,我们要不要去选择?FP8要不要?FP4要不要?还有Vector Quanti要不要,等等,这些都要去做选择。这些选择不能是一时兴起,我觉得重要的是不能因为别人都有,从众心理不能有。因为如果人家都有,你做一个缝合怪出来,是非常ugly的方案。所以我们需要一套严格的分析平台,先做精度仿真,一层层去确认未来需要什么样计算精度,这些计算精度分布在什么样的计算器件上。

同时计算的特点是怎么样的?我们通过这一套平台驱动了纳什架构下对Transformer的优化,通过Benchmark观察识别出带宽会有非常大的压力,我们要去加带宽。在计算的存储缓存的部件上,我们发现带宽和缓存两者会是一个相互交叉影响的关系,我们需要找到最佳的带宽与存储的配比,同时兼顾芯片的成本因素。具体反映到计算单元上,什么样的计算占到了什么比重?它算得不够快,在软件编译优化的层面,有没有条件去隐藏到那些算得更慢的单元里面去?比如大型的矩阵计算需要更多的时间,像Softmax、Layout这些不够快的激活计算,是不是能够掩盖在某一些matmul的计算里去,所以又能省一些。不用把每一个东西都做得那么强,只需要最终的算法效果是有竞争力的。所以我们在这个过程中要做很多的选择,通过Benchmark驱动,把未来我们需要加入的能力都在这样的仿真平台去验证。

当然这样的仿真平台还有一个好的效果是什么?当下一代芯片平台真正推出来之前,我们基本上能力验证完备,可以快速实现原型化,进而加速产品化,使得我们的开发套件早早地领先于物理芯片。比如我可以快速提供仿真开发环境给我们应用的合作伙伴。在芯片回片之前,我们就可以基于它做一些前期的算法应用的原型验证,到芯片回来的时候快速一切,理想状态就是这么丝滑。这是一个行业内芯片厂商与未来应用厂商的异步开发模式,整体上还是效率的问题,这将推向这个行业走向更高的迭代效率。

我今天要分享的内容就这些,谢谢大家。