SDAccel矩阵乘法优化(二)
从一个矩阵乘法的例子一步一步进行功能设计与性能优化。
mmult实现及优化步骤
矩阵乘法优化步骤
步骤
实现功能
关键概念/ Keywords
1、cpu实现
即在host端实现简单的矩阵乘法,便于比对数据与性能对比
---
2、OpenCL实现
在device端实现基于OpenCL的FPGA矩阵乘法硬件设计.
OpenCL接口函数
3、加入Local Memory
采用 Local Memory 减少数据访存次数
内核优化
局部内存
4、实现读写的突发传输
采用突发传输的方式更好的实现DDR与 Local Memory数据的读写访问
内核优化
突发读/写
5、数组分割
通过循环展开与数组分割的方式,实现更好的计算性能
数组分割
循环展开
流水线设计
方案分析及优化思路一(Local Memory)
首先,我们先进行访存上的优化。原始版本的矩阵乘法实现虽然简单,但是在进行计算的过程中需要频繁的与DDR进行数据交互,但是DDR与FPGA进行交互的过程中是十分耗费时间与功耗的,因此,我们需要在FPGA上开一个局部的存储空间,先将数据从DDR搬运到FPGA片上的存储空间上,然后再进行计算,计算的过程数据在片上的空间进行索引,最后将计算完的数据再统一搬运回DDR上。这样,在片上的计算过程就不会频繁的受到DDR与FPGA访存慢的限制。
代码实现
#define MAX_SIZE 64
kernel __attribute__((reqd_work_group_size(1, 1, 1)))
void mmult( __global int* in1, //Read-only input matrix1
__global int* in2, //Read-only input matrix2
__global int* out, //Output matrix
int dim //One dimension of the matrix
)
{
//Local memory to store input matrices
//Local memory is implemented as BRAM memory blocks
//MAX_SIZE * MAX_SIZE buffer is created because the size
//need to be known at compile time
__local int local_in1[MAX_SIZE][MAX_SIZE];
__local int local_in2[MAX_SIZE][MAX_SIZE];
__local int local_out[MAX_SIZE][MAX_SIZE];
//Read the input data from DDR memory to local memory
read_in1: for(int iter = 0, i = 0, j = 0; iter < dim * dim; iter++, j++){
if(j == dim){ j = 0; i++; }
local_in1[i][j] = in1[iter];
local_in2[i][j] = in2[iter];
}
//Reads the input_data from local memory, performs the computations
//and writes the data to local memory
for(int i = 0; i < dim; i++){
for(int j = 0; j < dim; j++){
local_out[i][j] = 0;
write_data: for(int k = 0; k < dim; k++){
local_out[i][j] += local_in1[i][k] * local_in2[k][ j];
}
}
}
//Write the data from local memory to DDR memory
write_out: for(int iter = 0, i = 0, j = 0; iter < dim * dim; iter++, j++){
if(j == dim){ j = 0; i++; }
out[iter] = local_out[i][j];
}
}
实验结果分析
- vivado hls log文件分析
WARNING: [XFORM 203-542] Cannot flatten a loop nest 'Loop-2.1' (/home/lab611/workspace/xuke/mmult_test/src/mmult.cl:86:22) in function 'mmult' :
WARNING: [XFORM 203-542] the outer loop is not a perfect loop.
INFO: [XFORM 203-541] Flattening a loop nest 'Loop-2' (/home/lab611/workspace/xuke/mmult_test/src/mmult.cl:85:18) in function 'mmult'.
INFO: [XFORM 203-811] Inferring bus burst write of variable length on port 'gmem' (/home/lab611/workspace/xuke/mmult_test/src/mmult.cl:97:9).
INFO: [HLS 200-111] Finished Architecture Synthesis Time (s): cpu = 00:00:00.79 ; elapsed = 00:00:00.88 . Memory (MB): peak = 494.316 ; gain = 156.758 ; free physical = 19901 ; free virtual = 45272
INFO: [HLS 200-10] Starting hardware synthesis ...
INFO: [HLS 200-10] Synthesizing 'mmult' ...
WARNING: [SYN 201-107] Renaming port name 'mmult/out' to 'mmult/out_r' to avoid the conflict with HDL keywords or other object names.
INFO: [HLS 200-10] ----------------------------------------------------------------
INFO: [HLS 200-42] -- Implementing module 'mmult'
INFO: [HLS 200-10] ----------------------------------------------------------------
INFO: [SCHED 204-11] Starting scheduling ...
INFO: [SCHED 204-61] Pipelining loop 'read_in1'.
WARNING: [SCHED 204-68] Unable to enforce a carried dependence constraint (II = 1, distance = 1, offset = 1)
between bus request on port 'gmem' (/home/lab611/workspace/xuke/mmult_test/src/mmult.cl:80) and bus request on port 'gmem' (/home/lab611/workspace/xuke/mmult_test/src/mmult.cl:79).
INFO: [SCHED 204-61] Pipelining result: Target II: 1, Final II: 2, Depth: 138.
INFO: [SCHED 204-61] Pipelining loop 'write_data'.
INFO: [SCHED 204-61] Pipelining result: Target II: 1, Final II: 1, Depth: 8.
INFO: [SCHED 204-61] Pipelining loop 'write_out'.
INFO: [SCHED 204-61] Pipelining result: Target II: 1, Final II: 1, Depth: 4.
INFO: [SCHED 204-11] Finished scheduling.
- HLS Report
- 综合结果分析
* 首先,硬件代码没有优化指令,不需要关注指令是否实现。
* 然后,相比于原始版本的矩阵乘法实现,Local Memory
的实现方式首先将整体的代码风格进行了调整,切分成三段并列的for
循环形式。从Pipleline
的角度考虑:第一段for循环pipeline
成功;第二段的for循环只有write_data
的for循环成功,最外层的两个for循环成功完成flatten
但是write_data
与次外层的for循环因为含有LOOP BODY
的原因,无法成功flatten
,因此也无法完成整体的pipeline
;第三段for循环pipeline
成功。
* 从pipeline成功后的II角度考虑:第一段for循环pipeline
后的II=2
,原因依然是 gmem carry dependency
;第二三段for循环pipeline
后的II=1
。
- 硬件仿真结果
参考
xilinx github Xilinx/SDAccelExamples/cputo_fpga ug1253 SDx Pragma Reference Guide 2017.2 ug1207 SDAccel Environment Optmizaton Guide
- 通过实例模拟ASP.NET MVC的Model绑定机制:数组
- 《全球贸易信息动态》
- .NET Core的日志[3]:将日志写入Debug窗口
- Code2Cloud:比ALM中断更大
- .NET Core的日志[4]:将日志写入EventLog
- 微信小程序不行了?看小马哥带你忆童年
- ASP.NET MVC三个重要的描述对象:ControllerDescriptor和ActionDescriptor的创建
- .NET Core的日志[5]:利用TraceSource写日志
- 物联网芯片正在积极开发 明年将得到爆发
- 韩国全球首测5G网络下自动驾驶 为汽车安全保驾护航的竟是路灯
- 通过与Quickbuild和Mist.io的持续集成实现云管理和使用监控
- .NET Core的文件系统[1]:读取并监控文件的变化
- ASP.NET MVC以ValueProvider为核心的值提供系统: ValueProviderFactory
- 云本机应用程序成熟度的模型
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法