Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add doc #24

Merged
merged 12 commits into from
Jun 4, 2019
25 changes: 25 additions & 0 deletions docs/bconv_CN.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
## Bit-packing
在执行二值卷积之前,网络需要手动插入一层`Binarize`。是指将 N 个 32 位的 float/integer,根据和 0 的大小关系,二值化为 N 个 bit (即 0 或 1),并打包成一个 N-bit 的整体,例如对 128 个浮点数进行 bit-packing 之后,就会产生一个 128-bit 的操作数。这一步叫做 bit-packing,做了这一步,后续才可以进行位运算 xnor/xor。

Bit-packing 的具体实现在

* https://github.com/JDAI-CV/dabnn/blob/master/dabnn/bitpack.h#L20 (高优化版,针对 128 和以上 channel 数的 tensor)
* https://github.com/JDAI-CV/dabnn/blob/master/dabnn/bitpack.h#L204 (低优化版,针对 128 channel 以下的 tensor)

高优化版和低优化版的性能差距在 4 倍左右。在高优化版中,bit-packing 算法直接利用 IEEE 754 float 和 int32 的符号位,而不需要把每一个数都和 0 比较,并使用了 SIMD 指令加速这一算法。值得一提的是,使用 SIMD 指令进行 bit-packing 后,输出的 N-bit 操作数的 N 个 bit 和 N 个输入不是按顺序对应的,但只要 xnor/xor 的两个操作数的每个 bit 一一对应,就不会对运算产生任何影响,因此,在适用高优化 bit-packing 的场景下,我们会对 weight 进行重排,使它的每个 bit 和 input 的每个 bit 一一对应,这一步的具体代码在 https://github.com/JDAI-CV/dabnn/blob/master/dabnn/net.cpp#L82。

卷积实现有很多种办法,dabnn 提供了如下两种优化实现。

## BGEMM

GEMM 是实现浮点卷积的通用方法。它要求先用 [im2col](https://github.com/JDAI-CV/dabnn/blob/master/dabnn/im2col.h) 重排输入,经过 im2col 之后,卷积即可被表示为矩阵和矩阵的乘法,即 GEMM。GEMM 的加速方法在 CNN 火热起来之前,就已经得到了深入的研究。不过在二值卷积中,不能利用已有的 GEMM 库,因为它们是为 double、float 或 integer 准备的,因此 dabnn 实现了 BGEMM (Binary GEMM)。它的优点是性能不低,实现方便,一套 GEMM 代码即可处理所有的情况。

BGEMM 的具体实现在 https://github.com/JDAI-CV/dabnn/blob/master/dabnn/bgemm.h。

## Binary Direct Convolution

然而 BGEMM 在 ARM 设备上并不高效,因为二值乘-加操作中,加法需要两步 - bitcount 和普通的加法。Bitcount 用来得到一个 N-bit 操作数中有多少 bit 是 1。在 ARMv8 设备上,bitcount 需要两条指令,ARMv7 设备上需要更多条指令。这大大限制了 BGEMM 的速度。因此 dabnn 提出了直接卷积的方法,称为 Binary Direct Convolution (BDC),它是指直接按照卷积的定义来计算卷积。在 BDC 中,通过一个简单的变换,大部分 bitcount 指令会被消除。它的优点是性能比 BGEMM 更高,但不能像 BGEMM 一样用一套代码覆盖所有的情况。

关于 BDC 如何消除大部分 bitcount 指令,请留意我们即将 publish 的 paper。

BDC 的具体实现在 https://github.com/JDAI-CV/dabnn/blob/master/dabnn/bconv.h。
20 changes: 20 additions & 0 deletions docs/design_CN.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
## 背景和发展方向

二值网络比较年轻,最初的两篇文章是 2016 年的 [Binary Neural Networks](https://arxiv.org/abs/1602.02830) 和 [XNOR-Net](https://arxiv.org/abs/1603.05279)。后续的工作中,[Bi-Real Net](https://arxiv.org/abs/1808.00278) 提出了一些精度提升方法,[BENN](https://arxiv.org/abs/1806.07550v2) 用 ensemble 方法进一步提升了 BNN 在分类任务上的表现,结果甚至超过单精度浮点模型。

但是从移动端工程应用的角度来看,定点网络可以节省数十倍的内存、提升数倍推理速度,同时降低十倍以上能耗。这意味着原本设备充电一次只能用一个小时,现在理论上可以用十小时以上。能耗相关可参见[相关测试](https://camo.githubusercontent.com/e725038be60ce4bb698b22480603b636a92beeaf/687474703a2f2f66696c652e656c656366616e732e636f6d2f776562312f4d30302f35352f37392f7049594241467373565f5341504f63534141435742546f6d6531633033392e706e67)。

综合算法和工程来看,部分二值网络实用意义和竞争优势可能在以下两点:

1. 与已量产设备融合。嵌入式设备在设计过程中,为了节约成本往往会做成“刚好够用”的状态。二值卷积在计算过程中既不需要大量的 SIMD 乘法操作,内存需求也远低于 8bit 模型,对原有系统干扰极小;
2. 在分类任务中以混合精度的方式替换已有方法。

卷积曾出现过很多变种,但是其中大部分已被历史淘汰。BNN 要想避免此命运,最简单的方法莫过于尽快落在某个产品或项目上,证明自己的价值。


## 软件架构
在使用流程和软件结构方面,dabnn 和已开源的推理库(如 [ncnn](https://github.com/Tencent/ncnn)、[Tengine](https://github.com/OAID/Tengine)、[FeatherCNN](https://github.com/Tencent/FeatherCNN) 等)差距不大:

1. 模型训练可使用任意一种可以导出 ONNX 模型的框架,但需要注意的是,二值卷积是自定义操作,为了让模型中二值卷积可以被 dabnn 正确识别,请看 [onnx2bnn_CN.md](docs/onnx2bnn_CN.md)。
1. 部署模型前需要把 onnx 格式转换成 dabnn 内部格式。在转换过程中,会把二值卷积的权重转换为 1-bit (而不是默认的 32-bit),大大减小模型文件的体积。流程和**注意事项**可参照 [onnx2bnn_CN.md](docs/onnx2bnn_CN.md);
3. 二值卷积实现请查阅 [bconv_CN.md](docs/bconv_CN.md)
31 changes: 31 additions & 0 deletions docs/onnx2bnn_CN.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
## 关于 ONNX

ONNX (Open Neural Network Exchange) 是一个独立于训练框架的模型格式,[众多框架和工具](http://onnx.ai/supported-tools) 支持 ONNX 格式。

## 模型转换流程

1. 识别二值卷积,对二值卷积的 weight 进行 bit-packing。dabnn 开发者给 onnx 增加了多个 optimizer,用来识别二值卷积,具体实现可参考 https://github.com/daquexian/onnx/tree/optimizer_for_bnn/onnx/optimizer/passes 中的 dabnn_*.h。关于 bit-packing 可以参考 [这篇文档](docs/bconv_CN.md);

1. 修改紧跟着二值卷积的 BN 层的权重。因为 bit 只有 1 和 0 两个值,所以二值卷积中的 -1 被用 0 表示,bitcount 可以得到一个 N-bit 操作数中,值为 1 的 bit 的数量,这忽略了 -1 的存在。具体来说,设 a 为一个 N-bit 操作数,b 是 a 中值为 1 的 bit 数量,c 是 a 中值为 0 的 bit 数量(即 -1 的数量)

在计算卷积时,我们应该得到的值是

> b - c = b - (N - b) = 2 * b - N = 2 * bitcount(a) - N

这个值可以经过一个对 bitcount(a) 的线性变换得到,因此我们将这个变换融合进二值卷积之后的 BN 层之中。

具体实现在 https://github.com/JDAI-CV/dabnn/blob/master/tools/onnx2bnn/OnnxConverter.cpp#L530。

1. 其他 Layer 正常处理。

## 注意事项(必看)

模型转换过程中有些规则或限制需要额外说明。

1. **二值卷积的输入 channel 暂时需要是 128 的倍数或 64**;

1. 二值卷积是自定义操作,因此可能存在多种实现,网上存在的二值卷积的自定义实现几乎全部是错的,例如它们用 0 进行 pad,而忽略了二值卷积的输入只能有 +1 和 -1。dabnn 开发者提供了一个[标准的二值卷积 PyTorch 实现](https://gist.github.com/daquexian/7db1e7f1e0a92ab13ac1ad028233a9eb),我们建议所有二值网络的训练者使用这个实现,或是按照这个实现来在他们用的训练框架中自行实现二值卷积;

1. onnx2bnn 有多种针对二值卷积的识别模式,例如会根据卷积的权重(是否为 +1/-1)识别、根据 Sign operator 识别,在用户选择 aggressive 模式时,甚至可以识别上一条所述的非正确的二值卷积(但在运算时仍会以 -1 而不是 0 来 pad,因此会导致结果不完全一致)。具体请看 [这篇文档](https://github.com/JDAI-CV/dabnn/wiki/Train,-export-and-convert-a-dabnn-model);

1. 目前暂时不支持 `group` 参数。