PS端Layer20_Route层处理
PS端Layer20_Route层处理
目的
- Layer20_Route层是一个拼接层,它将Layer19和Layer8的输出数据拼接在一起,作为Layer21的输入数据。
- Layer19和Layer8的输出数据是量化后的数据,它们有不同的量化系数(scale和zero point)。
- 为了保证Layer21能够正确处理Layer20的输入数据,需要将Layer8的输出数据的量化系数统一到Layer19的量化系数。
- Layer8的输出数据同时也作为Layer9到Layer15的输入数据,所以需要保留原来的量化系数。
预处理
-
在PS端实现Layer20_Route层之前,需要先准备好Layer19和Layer8的输出数据。这两个层都是卷积层,它们分别对应着模型中不同尺度和特征的数据。
Layer19是一个上采样层,它将输入数据放大了两倍,输出数据的尺寸是26x26x128。
Layer8是一个普通卷积层,它对输入数据进行了卷积、批归一化和激活操作,输出数据的尺寸是13x13x256。
-
为了实现Layer20_Route层,需要将Layer19和Layer8的输出数据拼接在一起。
具体来说,就是将Layer8的输出数据按照最后一个维度(即通道数)追加到Layer19的输出数据后面,形成一个新的数据矩阵,其尺寸为26x26x384。这样做相当于将两个不同尺度和特征的数据融合在一起,增强了模型对目标物体的检测能力。
-
在PS端进行数据拼接之前,还需要考虑一个细节问题,就是如何处理两个层输出数据之间的量化差异。
由于在PS端使用了整数类型的数据来存储和计算卷积层的输出结果,所以需要给每个层分配一个量化系数(scale)和一个零点(zero point),用来表示浮点数类型和整数类型之间的转换关系。
例如,如果一个浮点数类型的数据乘以scale再加上zero point就等于对应的整数类型的数据,则我们称这个scale和zero point为该层输出数据的量化参数。
-
由于每个卷积层都有自己的量化参数,所以Layer19和Layer8的输出数据的量化参数是不一样的。
这就意味着,如果直接将两个层的输出数据拼接在一起,那么它们之间的数值大小和含义是不一致的,这会影响下一个层(Layer21)的计算结果。
为了解决这个问题,需要在PS端进行数据量化的统一,即将两个层的输出数据都转换为相同的量化参数,再进行数据拼接
原理
- Layer19和Layer8都是卷积层,包含卷积、batch norm和激活三个操作
- 在实现时,卷积和batch norm已经融合成一个卷积操作,只需要进行卷积和激活两个操作
- 卷积和激活的输出都有各自的量化系数,互不影响
- 如果要将Layer8的数据量化到Layer19的量化系数,只需要在激活时使用Layer19的量化系数即可
实现数据量化的统一:
将Layer8的输出数据的量化参数统一到Layer19的输出数据的量化参数。
具体来说,就是将Layer8的输出数据先除以它自己的scale再减去它自己的zero point,得到一个浮点数类型的中间结果,然后再乘以Layer19的scale再加上Layer19的zero point,得到一个整数类型的最终结果。
这样做相当于将Layer8的输出数据从它自己的量化空间转换到了Layer19的量化空间,使得两个层输出数据之间具有了相同的数值大小和含义。
为了实现上述操作,需要在PS端进行一些代码和参数的修改:
首先,需要在Python代码中生成一个新的bin文件,用来存储Layer8经过量化转换后的输出数据。这个bin文件命名为L8_20_R.bin,表示它是Layer8到Layer20之间经过Route层处理后的结果。
其次,需要在Verilog代码中修改一些配置和地址信息,用来指定Layer8_20_R.bin文件在SD卡中的位置和大小。
最后,需要在Verilog代码中添加一些逻辑判断和控制信号,用来实现Layer8输出数据在PS端进行两次计算(一次是正常计算Layer9到Layer15之间的结果,另一次是计算Layer8_20_R.bin文件)。
步骤
- 在Python代码中,输出Layer19、Layer8和Layer20的数据,观察它们的scale和zero point
- 发现Layer19使用了Layer18的激活scale和zero point,而Layer8使用了自己的激活scale和zero point
- 为了让Layer8和Layer19的数据有相同的scale和zero point,需要对Layer8进行重新量化
- 在Python代码中,使用一个脚本文件,将Layer8的卷积输出恢复成浮点数,然后再用Layer18的激活scale和zero point进行量化,生成一个新的bin文件
- 在PS端,修改配置文件,让Layer7计算两次,第一次得到Layer8-20的结果,第二次得到正常的Layer8的结果
- 在PS端,修改配置文件,将Layer8-20的结果存储在紧挨着Layer19结果的地址后面
- 在PS端,修改配置文件,将Layer8-20的激活bin文件替换成新生成的bin文件
举例
- Layer19的数据:
0.7950, 0.7950, 0.7950, ..., 0.7950, 5
- Layer8的数据:
0.2315, 0.2315, 0.2315, ..., 0.2315, 14
- Layer20的数据:
0.7950, 0.7950, ..., 0.7950, 5, 0.7950, ..., 0.7950, 5
- Layer18的激活scale和zero point:
0.7950, 5
- Layer8-20的bin文件:
L820-R.bin
- Layer7计算两次的配置:
L7-C.bin L7-B.bin L820-A.bin L820-R.bin
L7-C.bin L7-B.bin L8-A.bin L8-R.bin
- Layer8-20存储地址:
15200000 + i * 256 * 128 * 2
- Layer8-20激活bin文件替换:
L820-A.bin
->L820-R.bin
表格:
- Layer19和Layer8的输出数据如下(仅显示前四个元素):
Layer | Data | Scale | Zero point |
---|---|---|---|
19 | 10, 12, 14, 16 | 0.7950 | 5 |
8 | 20, 22, 24, 26 | 0.2315 | 14 |
- 使用Python端重新量化Layer8的输出数据,得到如下结果:
Layer | Data | Scale | Zero point |
---|---|---|---|
19 | 10, 12, 14, 16 | 0.7950 | 5 |
8_20 | -1, -1, -1, -1 | 0.7950 | 5 |
- 将两个输出数据拼接在一起,得到如下结果:
Layer | Data | Scale | Zero point |
---|---|---|---|
20 | 10, 12, 14, 16, -1, -1, -1, -1 | 0.7950 | 5 |
图解
Layer20_Route层的Verilog代码实现
- 使用
assign
语句将Layer19和Layer8两个卷积层的输出拼接在一起,类似于Verilog中的位拼接操作符 - 使用
case
语句来实现量化系数的统一,即根据Layer8卷积层的输出值,选择对应的Layer19激活量化系数的值 - 使用
always
语句来实现数据的存储和读取,根据不同的层号和地址,选择不同的数据源和目标 - 代码框架:
// layer19 and layer8
assign layer20_output = {layer19_output, layer8_output};
always @(*) begin
case(layer8_output)
0: layer8_output_unified = 5;
1: layer8_output_unified = 6;
2: layer8_output_unified = 7;
// ...
default: layer8_output_unified = 5;
endcase
end
always @(posedge clk) begin
if (rst) begin
end else begin
case(layer_num)
// ...
7: begin // layer8
if (write_en) begin
mem_layer8[addr] <= data_in;
end else begin
data_out <= mem_layer8[addr];
end
end
13: begin // layer20_route
if (write_en) begin
mem_layer20[addr] <= {layer19_output, layer8_output_unified};
end else begin
data_out <= mem_layer20[addr];
end
end
// ...
default: begin
end
endcase
end
end