作者丨黎明灰烬
源自|https://zhuanlan.zhihu.com/p/80361782
引言
卷积(Convolution)是神经网络的核心计算之一,它在计算机视觉方面的突破性发展引领了深度学习的热潮。卷积的变种丰富,计算繁杂,神经网络运行时大部分时间都耗费在计算卷积,网络模型的发展在持续增多网络的深度,因此呢优化卷积计算就显出尤为重要。
随着技术的发展,科研人员提出了多种优化算法,包含 Im2col、Winograd 等等。本文首要定义卷积神经网络的概念,继而简要介绍几种平常的优化办法,并讨论作者在该行业的有些经验。 大部分时间都耗费在计算卷积链接:https://arxiv.org/abs/1807.11164Im2col 链接:https://www.mathworks.com/help/images/ref/im2col.htmlWinograd 链接:https://www.intel.ai/winograd/#gs.avmb0n
卷积神经网络的概念
卷积神经网络(Convolution Neural Networks, CNN)的概念拓展自信号处理行业的卷积。信号处理的卷积定义为
(1)
因为对叫作性
卷积计算在直觉上很难理解,其可视化后如图一所示。图中红色滑块在移动过程中与蓝色方块的积绘制成的三角图案即为卷积结果 (∗)() 在各点上的取值。
图一:信号处理中的卷积 信号处理中的卷积链接:https://jackwish.net/convolution-neural-networks-optimization.html
公式1的离散形式为 :
(2)
将该卷积拓展到二维空间就可得到神经网络中的卷积,可简写为:
(3)
其中 为卷积输出, 为卷积输入, 为卷积核。该计算的动态可视化能够参考 conv_arithmetic,这儿再也不介绍。 conv_arithmetic 链接:https://github.com/vdumoulin/conv_arithmetic
当应用到计算机视觉中处理照片时,照片的通道(Channel)能够对二维卷积简单堆叠,即:
(4)
其中 c 是输入的通道。这便是在三维张量中应用二维卷积的计算。
非常多时候,公式描述显出不是很直观,图二是堆叠的二维卷积的可视化。其中,与输入、输出、卷积核关联的标记带有前缀 I、O、K。另外,本文图例对输出、输入、卷积核三者的着色通常为:橙色、黄色、绿色。
图二:卷积计算定义
其中张量的内存布局为 NHWC 时,卷积计算相应的伪代码如下。其中外三层循环遍历输出 C 的每一个数据点,针对每一个输出数据都需要经由内三层循环累加求和得到(点积)。 for (int oh = 0; oh < OH; oh++) {
for (int ow = 0; ow < OW; ow++) {
for (int oc = 0; oc < OC; oc++) {
C[oh][ow][oc] = 0;
for (int kh = 0; kh < KH, kh++){
for (int kw = 0; kw < KW, kw++){
for (int ic = 0; ic < IC, ic++){
C[oh][ow][oc] += A[oh+kh][ow+kw][ic] * B[kh][kw][ic];
}
}
}
}
}
}
和矩阵乘的优化办法类似,咱们亦可针对该计算进行向量化、并行化、循环展开的基本的优化操作。卷积的问题在于其 和 通常不超过 5 ,这不易向量化,而计算特征又有输入在空间维度存在数据复用。该计算的繁杂性引起产生了几种优化办法,下面咱们介绍几种。 和矩阵乘的优化办法链接:https://jackwish.net/gemm-optimization.html
Im2col 优化算法
做为初期的深度学习框架,Caffe 中卷积的实现采用的是基于 im2col 的办法,迄今仍是卷积重要的优化办法之一。
Im2col 是计算机视觉行业中将照片转换成矩阵的矩阵列(column)的计算过程。从上一节的介绍中能够看到,二维卷积的计算比较繁杂很难优化,因此呢在深度学习框架发展的初期,Caffe 运用 Im2col 办法将三维张量转换为二维矩阵,从而充分利用已然优化好的 GEMM 库来为各个平台加速卷积计算。最后,再将矩阵乘得到的二维矩阵结果运用 Col2im 将转换为三维矩阵输出。 Caffe 链接:http://caffe.berkeleyvision.org/Caffe 运用 Im2col 办法将三维张量转换为二维矩阵链接:https://github.com/Yangqing/caffe/wiki/Convolution-in-Caffe:-a-memo
算法过程
除非尤其说明,本文默认采用的内存布局形式为 NHWC 。其他的内存布局和详细的转换后的矩阵形状或许略有差异,但不影响算法本身的描述。
图三:Im2col 算法计算卷积的过程
图三是运用 Im2col 算法计算卷积的过程示例,详细的过程包含(简单起见忽略 Padding 的状况,即认为 =,=: 将输入由 ×× 按照卷积的计算特性展开成 (×)×(××) 形状的二维矩阵。显然,转换后运用的内存空间相比原始输入多约 ∗−1 倍。权重的形状通常为 ××× 四维张量,能够将其直接做为形状为 ()×(××)) 的二维矩阵处理。针对准备好的两个二维矩阵,将 (××) 做为累加求和的维度,运行矩阵乘能够得到输出矩阵 (×)×()。这一过程能够直接运用各样优化过的 BLAS(Basic Linear Algebra Subprograms)库来完成。输出矩阵 (×)×() 在内存布局视角即为预期的输出张量 ×× 。
Im2col 计算卷积运用 GEMM 的代价是额外的内存开销。这是由于原始卷积计算中,卷积核在输入上滑动以计算输出时,相邻的输出计算在空间上复用了必定的输入输出。而用 Im2col 将三维张量展开成二维矩阵时,这些本来能够复用的数据平坦地分布到矩阵中,将输入数据复制了 ∗−1 份。
当卷积核尺寸 × 是 1×1 时,以上过程中的 和 能够消去,即输入转换后形状为 (×)×(),卷积核形状为 ()×(),卷积计算退化为矩阵乘。重视观察,针对这种状况,Im2col 过程实质上并无改变输入的形状,因此呢矩阵乘操作可以直接在原始输入上运行,从而省去内存组织的过程(即以上过程中的 1、2、4 步)。
内存布局与卷积性能
神经网络中卷积的内存布局重点有 NCHW 和 NHWC 两种,区别的内存布局会影响计算运行时拜访存储器的模式,尤其是在运行矩阵乘时。本小节分析采用 Im2col 优化算法时计算性能性能和内存布局的关系。 尤其是在运行矩阵乘时链接:https://jackwish.net/gemm-optimization.html
在完成 Im2col 转换后,得到用于运行矩阵乘的输入矩阵和卷积核矩阵。对计算过程施加矩阵计算中常用的数据划分、向量化等优化办法(关联定义请参考通用矩阵乘(GEMM)优化算法)。下面着重分析在这种场景下,区别内存布局对性能的影响。 通用矩阵乘(GEMM)优化算法链接:https://jackwish.net/gemm-optimization.html
首要思虑 NCHW 内存布局,将 NCHW 内存布局的卷积对应到矩阵乘 = 时, 是卷积核(filter), 是输入(input),各个矩阵的维度如图四所示。图中的 ,, 用于标记矩阵乘,即
,同期标记出它们和卷积计算中各个维度的关系。
图四:NCHW 内存布局卷积转换成的矩阵乘
对该矩阵施行划分后,咱们仔细分析局部性的表现,并标记在图四中。其中 Inside 暗示 4×4 小块矩阵乘内部的局部性,Outside 暗示在削减维度方向小块之间的局部性。 对输出而言,小块内访存局部性较差,这是由于每次向量化加载会加载四个元素,每次加载都会出现缓存缺失(Cache miss)。外边表现取决于全局计算方向——行优先则局部性较好,列优先则较差。输出的行径不是这儿的讨论终点,毕竟它无访存复用。对卷积核而言,小块内访存局部性较差,这和输出类似。当小块加载出现缓存缺失时,处理器会一次性加载一个缓存行(Cache line),这使得后续的若干个小块访存都能缓存命中(Cache hit),直到该缓存行所有命中后进入下一个缓存行的范围。因此呢小块外边局部性较好。对输入而言,小块内访存局部性表现一样欠好。然而区别于卷积核,小块中因缓存缺失加载到缓存中的缓存行数据只会被运用一次,由于这种内存布局中下一个小块的位置范围通常超出了一个缓存行。因此呢输入的几乎每次内存加载都会出现高速缓存缺失——Cache 无起到加速的功效,每次访存都需要到内存取数据。
因此呢,用 Im2col 处理卷积计算时,NCHW 布局对内存很不友好。
图五是与之相对的 NHWC 内存布局的示例。值得重视的是,NHWC 和 NCHW 中 、 矩阵所表率的张量出现了调换——=×(调换一下只是不想多画一张图)。详细的拆分方式仍然同样,亦正是上一小节中描述的过程所构建的矩阵。
图五:NHWC 内存布局卷积转换成的矩阵乘
类似地,分析三个张量的访存表现可知: 对输出而言,NHWC 和 NCHW 表现同样。对输入而言,小方块的内部局部性表现不是很好,由于几次向量加载都会出现缓存不命中;而外边局部性表现则较好,由于在削减维度滑动运用的内存是连续的。这种表现和 NCHW 中卷积核的表现同样,整体来看都是对高速缓存比较友好的内存布局。对卷积核而言,NHWC 的状况和 NCHW 中输入的状况类似,小块内和小块外的局部性都较差。
两种内存布局中的卷积核缓存表现并不是问题,由于卷积核在运行时期保持不变,能够在模型加载周期转换卷积核的内存布局,使其在小块外的内存对缓存友好(例如将 (××)×() 的布局转换为 ()×(×× )。这儿值得说明的是通常框架或引擎的运行都最少可分为两个周期:准备周期和运行周期。有些模型的预处理工作能够放在准备周期完成,例如重新排布卷积核的内存布局这种在运行周期保持不变的数据。
因此呢,当运用 Im2col 办法计算时,整体的访存表现取决于输入的状况,即 NHWC 的内存布局要比 NCHW 内存布局更加友好。咱们在实践过程中的一个实验显示,针对一个 1×1 卷积核的卷积,当采用类似的优化办法时,从 NCHW 转换为 NHWC 能够将高速缓存缺失率从约 50% 降低到 2% 上下。这种程度的加强能够大幅改进软件的运行性能(这儿特指不运用尤其设计过的矩阵乘优化办法)。
空间组合优化算法
Im2col 是一种比较朴素的卷积优化算法,在无精心处理的状况下会带来很强的内存开销。空间组合(Spatial pack)是一种类似矩阵乘中重组内存的优化算法。 矩阵乘中重组内存链接:https://github.com/flame/how-to-optimize-gemm/wiki#packing-into-contiguous-memory
图六:空间组合优化算法对计算的划分
空间组合优化算法是一种基于分治法(Divide and Conquer)的办法——它基于空间特性将卷积计算划分为若干份,分别处理。图六所示是在空间上将输出、输入划分为四份。
划分后,一个大的卷积计算被拆分为若干个小的卷积计算。虽然在划分的过程中计算总量不变,但计算小矩阵时访存局部性更好,能够借由计算机存储层次结构得到性能提高。这经过图七中的过程来完成。该过程和上节中 Im2col 重组内存的过程类似: 在 H 和 W 维度划分,将形状为 ××× 的输入张量拆分为 ℎ∗ 个(两个方向分别拆分 ℎ 和 次)形状为 ×/ℎ×/× 的张量,分别将这些小的张量组织为连续内存;将得到的 ℎ∗ 个输入张量分别和卷积核做二维卷积操作,就可得到 ℎ∗ 个形状为 ×/ℎ×/× 的输出张量;将这些输出张量重组内存布局得到最后形状为 ××× 的输出。
图七:空间组合计算的过程
值得重视的是,方便起见,上文的描述中忽略了 Padding 的问题。实质在过程 1 中将输入张量划分为若干个小张量时,除了将划分的小块中原始数据拷贝外,还需要将相邻的小张量的边界数据拷贝。详细而言,如图八所示,空间拆分得到的小张量的形状实质上是:
×(/ℎ+2(−1))×(/+(−1))×.(5)
这儿的 2(−1) 和 2(−1) 遵循 Padding 规则。规则为
时,它们能够忽略;规则为 SAME 时,位置于源张量边界的一边 Padding 补
,不在源张量边界的 Padding 则运用邻居张量的值。只要思虑一下卷积的计算原理,这是显而易见的。
图八:空间组合算法的划分细节
上面的三个示例图都是拆分为 4 份的状况,实质应用中能够拆为非常多份。例如能够拆成小张量边长为 4 或 8 ,从而方便编译器向量化计算操作。随着拆分出的张量越小,其局部性亦越高,消极功效是消耗的额外内存亦越多。这些额外内存是因为 Padding 引入的。当拆分为 ℎ∗h∗w份时,拆分后 Padding 消耗的内存为:
能够看到,随着拆分的粒度越小,额外消耗的内存越大。值得重视的是,当拆分到最细粒度时,即将在形状为 ××× 的输出的空间上拆分∗ 份时,空间组合退化为 Im2col 办法。此时在一个元素上的卷积即为矩阵乘计算一个输出元素时的点积。
只做空间划分时,划分与卷积核无关。而倘若在输出的通道维度划分,卷积核亦可做相应的拆分。通道维度的划分相当于固定空间划分后简单的堆叠,不会对影响内存消耗,但会影响局部性。针对区别规模的卷积,寻找合适的划分办法不是一件容易的事情。正如计算机行业的许多问题同样,该问题亦是能够自动化的,例如AutoTVM 能够在这种状况下寻找较优的划分办法。
AutoTVM 链接:
https://arxiv.org/abs/1805.08166
Winograd 优化算法
前两节介绍的两种算法,Im2col 在将三维张量组织成矩阵后调用 GEMM 计算库,这些计算库很大程度上运用有些基于访存局部性的优化;空间组合优化则本身便是利用局部性优化的办法。本小节介绍的 Winograd 优化算法则是矩阵乘优化办法中 Coppersmith–Winograd 算法的一种应用,是基于算法分析的办法。
这部分公式太多,排版不方便,有兴趣的话能够参考原文或其他关联文献。
参考原文链接:
https://jackwish.net/convolution-neural-networks-optimization.html
间接卷积优化算法
Marat Dukhan 在 QNNPACK(Quantized Neural Network PACKage)中推出了间接卷积算法(The Indirect Convolution Algorithm),似乎到日前为止(2019 年中)依然是所有已公开办法中最快的。近期作者发布了关联的文案来介绍其中的重点思想。
虽然该算法在 QNNPACK 中的实现重点用于量化神经网络(业界的其他量化优化方法都比较传统 TensorFlow Lite 运用 Im2col 优化算法、腾讯出品的 NCNN运用 Winograd 优化算法;OpenAI 出品的 Tengine 运用 Im2col 优化算法),但其是一种一样的优化算法设计。
本文写作时设计文案尚未公开,而理解该算法设计非常多细节内容,最好能结合代码理解。我的 QNNPACK fork 包括一个 explained 分支,其中对增多了对部分代码的注释,可作参考。
QNNPACK 链接:
https://github.com/pytorch/QNNPACK
TensorFlow Lite 运用 Im2col 优化算法链接:
https://github.com/tensorflow/tensorflow/blob/v2.0.0-beta1/tensorflow/lite/kernels/internal/optimized/integer_ops/conv.h
NCNN运用 Winograd 优化算法链接:
https://github.com/Tencent/ncnn/blob/20190611/src/layer/arm/convolution_3x3_int8.h
Tengine 运用 Im2col 优化算法链接:
https://github.com/OAID/Tengine/blob/v1.3.2/executor/operator/arm64/conv/conv_2d_fast.cpp
我的 QNNPACK fork 链接:
https://github.com/jackwish/qnnpack
计算工作流
间接卷积算法的有效工作败兴一个关键的前提——网络连续运行时,输入张量的内存位置保持不变。这一特性其实比较容易满足,即使位置真的需要变化,亦能够将其拷贝到固定的内存区域中。
图九:间接卷积算法工作流
图九是间接卷积算法工作流的仔细过程。最左侧部分暗示多个输入运用相同的输入缓冲区(Input Buffer)。间接卷积算法会在该输入缓冲区基本上构建如图十的「间接缓冲区」(Indirect Buffer),而间接缓冲区是间接卷积算法的核心。如图中右侧,在网络运行时,每次计算出 × 规模的输出,其中 为将 × 视作一维后的向量化规模。通常 × 为 4×4、8×8 或 4×8 。在计算 × 规模体积的输出时,经由间接缓冲区取出对应运用的输入,并取出权重,计算出结果。计算过程等价于计算 × 和 × 矩阵乘。
在实现中,软件的执行过程分为两部分: 准备周期:加载模型,配置输入缓冲区;重排权重,使其内存布局适用于后续计算;运行周期:针对每一个输入,运行 ⌈∗/⌉∗⌈/⌉次核心循环,每次运用 GEMM 办法计算出 × 体积的输出。
图十:间接缓冲区
如关联章节的讨论,Im2col 优化算法存在两个问题,第1是占用海量的额外内存,第二是需要对输入进行额外的数据拷贝。这两点怎样才可处理呢?间接卷积算法给出的答案是间接缓冲区(Indirect Buffer),如图十右半所示。
图十是常规的 Im2col 优化算法和间接卷积优化算法的对比。正如关联小节介绍的那样,Im2col 优化算法首要将输入拷贝到一个矩阵中,如图十中 Input 的关联箭头,而后执行矩阵乘操作。间接卷积优化算法运用的间接缓冲区中存储的其实指的是向输入的指针(这亦是间接卷积优化算法需求输入内存位置固定的原由),在运行时按照这些指针就可模拟出类似于 Im2col 的矩阵计算过程。
间接缓冲区布局
间接缓冲区能够理解为是一组卷积核体积的缓冲区,共有 × 个,每一个缓冲区体积为 ×——每一个缓冲区对应着某个输出要运用的输入的位置。每计算一个空间位置的输出,运用一个间接缓冲区;空间位置相同而通道区别的输出运用相同的间接缓冲区,缓冲区中的每一个指针用于索引输入中 个元素。在计算时,随着输出的移动,选择区别的间接缓冲区,就可得到相应的输入位置。无需再按照输出目的的坐标计算要运用的输入的位置,这等同于预先计算位置。
图十一绘制了当 × 为 4 、 和 均为 3 时,间接缓冲区的实质运用办法与计算过程。图中命名为局部间接缓冲区意指日前思虑的是计算 × 时核心计算的过程。
当计算 × 体积的输出时,运用的输入为卷积核在对应输入位置上滑动 步所覆盖的趋于,即规模 ()×(+2(−1))× 的输入。在间接卷积算法中,这些输入内存由 M个间接缓冲区中的指针索引,共有 ×× 个。图十一中标识出了输入空间位置左上角输入和相应的输入缓冲区的对应关系。能够看到,这儿的 A、B、C、D 四个输入缓冲区,相邻的两个缓冲区所指向的位置区域有 (−)/ ,这儿即为 2/3 ,各个缓冲区中指针的坐标亦已标明。
图十一:间接缓冲区详解
图中将平面缓冲区绘制成三维形式(增加 IC 维度),意在说明间接缓冲区的每一个指针可索引 IC 个输入元素,而每一个间接缓冲区索引的内容即为与权重对应的输入内存区域。
进一步地,左上角的输入缓冲区摆列方式并不是最后的排布办法,实质上这些指针会被处理成图十一中部间接缓冲区的形式。将左上每一个缓冲区中的指针打散,就可得到 × 指针,将 A、B、C、D 四个缓冲区的区别空间位置的指针收集到一块,就可得到图中上部分的缓冲区摆列方式 ××。能够看到, A、B、C、D 四个缓冲区内部相同空间位置的指针被组织到了一块。图中中上部分是可视化的效果,中下部分则是间接缓冲区的真正组织方式。图中褐色和深黄色的着色对应着相同的输入内存或指针。值得重视的是,图例中 Stride 为 1,当 Stride 不为 1 时,重新组织后 A、B、C、D 相同空间的坐标(对应于在输入的坐标)不必定是连续的,相邻的空间位置横向坐标相差 体积。
运用间接缓冲区计算
咱们已然晓得了间接缓冲区的组织形式,以及其指针对应于输入内存的位置趋于,此刻来科研在计算过程中怎样运用这些缓冲区。
和上一小节同样,本小节的讨论大体都在计算 × 规模的输出,而这些输出要运用 个 × 体积的输入,其中有数据重用。此刻回顾一下 Im2col 的算法(如图十一中下左部分),当向量化运行计算时,针对 × 的矩阵乘计算,每次取出 × 规模的输入和 × 规模的权重,对该块运行矩阵乘就可得到相应的部分和结果。其中 是向量化计算在 K维度上的步进因子。
而卷积之因此能够运用 Im2col 优化算法,本质原由在于其拆解后忽略内存复用后的计算过程等价于矩阵乘。而间接缓冲区使得能够经过指针模拟出对输入的访存。在实质运行计算 × 输出的计算核(micro kernel)时,会有 个指针扫描输入。个指针每次从图十一中下部分的间接缓冲区结构中取出 个位置,即对应于输入 × 的内存,如图中右上角的布局。在这儿的步进中,仍是以 × 形式运行,其中 在 维度上运动。当这部分输入扫描完毕后,这 个指针从间接缓冲区中取出相应的指针,继续下一次对 × 输入内存的遍历,每次计算出 1/(∗) 的输出部分和。当这个过程运行 × 次后,即得到了 × 的输出。图十一右下角的伪代码对应了这一过程。因为间接缓冲区已然被组织成为了特定的形状,因此呢每次更新 个指针时,只需要从间接缓冲区指针(图中伪代码里的 p_indirect_buffer)中获取。
这个过程的规律很难理解,这儿再做一点弥补说明。当以上的 个指针持续运动扫描内存时,实质上是在扫描三维输入 Im2col 之后的矩阵。而输入缓冲区的特定是它将对二维矩阵的扫描转化为了对三维张量的扫描。对输入的扫描过程即是对图十一中上部分可视化的输入的扫描,联系左上和左下对应的输入关系,不难发掘它每次扫描输入中 × 块内存。值得重视的是,这儿的 × 由 个 1× 张量构成,它们之间 维度的间距为 。
这般一来,只要运行该计算核心 ⌈∗/⌉∗⌈/⌉ 次,就可得到所有输出。
间接卷积优化算法处理了卷积计算的三个问题,第1是空间向量化问题,第二是位置计算繁杂问题,第三是内存拷贝问题。通常计算卷积时都需要对输入补零(针对 × 不是 1×1 的状况),这个过程传统的办法都会出现内存拷贝。而间接卷积优化算法中的间接缓冲区能够经过间接指针巧妙地处理这一问题。在构造间接缓冲区时额外创建一起 1× 的内存缓冲区,其中填入零值,针对空间中需要补零的位置,将相应的间接指针指向该缓冲区,那样后续计算时即相当于已然补零。例如图十一中 A 的左上角对应于输入空间位置 (0,0) 的,当需要补零时该位置一定要为零值,此时只要修改间接缓冲区的位置就可。
讨论、总结与展望
至此,本文探讨了有些已然发布或开源的卷积神经网络的优化办法。这些优化办法或多或少地推动了深度学习技术在云端或移动端的应用,帮忙了咱们的平常生活。例如,近期上海人民乃至全中国人们头疼的垃圾归类问题,亦能够利用深度学习办法来帮忙人们认识怎样分类。 利用深度学习办法来帮忙人们认识怎样归类链接:https://news.mydrivers.com/1/633/633858.htm
从本文的集中优化办法中能够看到,卷积神经网络的优化算法依然能够包括在基于算法分析的办法和基于软件优化的办法。实质上,在现代计算机系统中,基于软件优化的办法,尤其是基于计算机存储层次结构的优化显出更为重要,由于其办法更易挖掘和应用。另一方面亦是由于现代计算机新增的专用指令已然能够抹除区别计算的耗时差异。在这种场景下,基于算法分析的办法要和基于软件优化的办法结合或许能取得更优的优化效果。
最后,本文讨论的优化办法都是通用的办法,而随着神经网络处理器(如寒武纪 MLU、Google TPU)的发展,以及其他通用计算处理器的拓展(如点积关联的指令:Nvidia GPU DP4A、Intel AVX-512 VNNI、ARM SDOT/UDOT ),深度学习的优化或许还值得继续投入资源。
寒武纪 MLU 链接:
https://www.jiqizhixin.com/articles/2019-06-20-12
Nvidia GPU DP4A 链接:
https://devblogs.nvidia.com/mixed-precision-programming-cuda-8/
Intel AVX-512 VNN 链接:
https://devblogs.nvidia.com/mixed-precision-programming-cuda-8/
本文写作过程中参考了以下(包含但不限于)资料:
QNNPACK
( 链接:https://github.com/pytorch/QNNPACK )
Convolution in Caffe
( 链接:https://github.com/Yangqing/caffe/wiki/Convolution-in-Caffe:-a-memo )
TensorFlow
Wikipedia
Fast Algorithms for Convolutional Neural Networks
( 链接:https://github.com/Tencent/ncnn )
NCNN
( 链接:https://github.com/OAID/Tengine )
Tengine
( 链接:https://jackwish.net/neural-network-quantization-introduction-chn.html )
神经网络量化简介
( 链接:https://jackwish.net/neural-network-quantization-introduction-chn.html )
通用矩阵乘(GEMM)优化算法
( 链接:https://jackwish.net/gemm-optimization.html )
The Indirect Convolution Algorithm
|