PS端Layer20_Route层处理

发布时间 2023-08-25 19:26:11作者: 李白的白

PS端Layer20_Route层处理

目的

  • Layer20_Route层是一个拼接层,它将Layer19和Layer8的输出数据拼接在一起,作为Layer21的输入数据。
  • Layer19和Layer8的输出数据是量化后的数据,它们有不同的量化系数(scale和zero point)。
  • 为了保证Layer21能够正确处理Layer20的输入数据,需要将Layer8的输出数据的量化系数统一到Layer19的量化系数。
  • Layer8的输出数据同时也作为Layer9到Layer15的输入数据,所以需要保留原来的量化系数。

预处理

  1. 在PS端实现Layer20_Route层之前,需要先准备好Layer19和Layer8的输出数据。这两个层都是卷积层,它们分别对应着模型中不同尺度和特征的数据。

    ​ Layer19是一个上采样层,它将输入数据放大了两倍,输出数据的尺寸是26x26x128。

    ​ Layer8是一个普通卷积层,它对输入数据进行了卷积、批归一化和激活操作,输出数据的尺寸是13x13x256。

  2. 为了实现Layer20_Route层,需要将Layer19和Layer8的输出数据拼接在一起。

    ​ 具体来说,就是将Layer8的输出数据按照最后一个维度(即通道数)追加到Layer19的输出数据后面,形成一个新的数据矩阵,其尺寸为26x26x384。这样做相当于将两个不同尺度和特征的数据融合在一起,增强了模型对目标物体的检测能力。

  3. 在PS端进行数据拼接之前,还需要考虑一个细节问题,就是如何处理两个层输出数据之间的量化差异。

    ​ 由于在PS端使用了整数类型的数据来存储和计算卷积层的输出结果,所以需要给每个层分配一个量化系数(scale)和一个零点(zero point),用来表示浮点数类型和整数类型之间的转换关系。

    例如,如果一个浮点数类型的数据乘以scale再加上zero point就等于对应的整数类型的数据,则我们称这个scale和zero point为该层输出数据的量化参数。

  4. 由于每个卷积层都有自己的量化参数,所以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文件)。

步骤

  1. 在Python代码中,输出Layer19、Layer8和Layer20的数据,观察它们的scale和zero point
  2. 发现Layer19使用了Layer18的激活scale和zero point,而Layer8使用了自己的激活scale和zero point
  3. 为了让Layer8和Layer19的数据有相同的scale和zero point,需要对Layer8进行重新量化
  4. 在Python代码中,使用一个脚本文件,将Layer8的卷积输出恢复成浮点数,然后再用Layer18的激活scale和zero point进行量化,生成一个新的bin文件
  5. 在PS端,修改配置文件,让Layer7计算两次,第一次得到Layer8-20的结果,第二次得到正常的Layer8的结果
  6. 在PS端,修改配置文件,将Layer8-20的结果存储在紧挨着Layer19结果的地址后面
  7. 在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

图解

graph LR L7[Layer7] -->|计算两次| L8[Layer8] L7 -->|计算两次| L820[Layer8_20] L820 -->|拼接| L20[Layer20] L19[Layer19] -->|拼接| L20 L18[Layer18] -->|upsample| L19 L9[Layer9] -->|concatenate| L15[Layer15] L10[Layer10] -->|concatenate| L15 L11[Layer11] -->|concatenate| L15 L12[Layer12] -->|concatenate| L15 L13[Layer13] -->|concatenate| L15 L14[Layer14] -->|concatenate| L15 style L820 fill:#f9f,stroke:#333,stroke-width:4px;

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