gmem carry dependency 分析

方案一

  • 方案一 源码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#define J_CNT       2
#define I_CNT 4
#define BUFFER_SIZE I_CNT*J_CNT

kernel __attribute__((reqd_work_group_size(1, 1, 1)))
void vector_add(__global int* c,
__global const int* a,
__global const int* b,
const int n_elements)

{
local int arrayA[BUFFER_SIZE];
local int arrayB[BUFFER_SIZE];
local int arrayC[BUFFER_SIZE];

__attribute__((xcl_pipeline_loop))
loop_1:
for(int i = 0; i < I_CNT; i++) {
//__attribute__((xcl_pipeline_loop))
loop_2:
for(int j = 0; j < J_CNT; j++) {
arrayA[i * J_CNT + j] = a[i * J_CNT + j];
arrayB[i * J_CNT + j] = b[i * J_CNT + j];
c[i * J_CNT + j] = arrayA[i * J_CNT + j]+arrayB[i * J_CNT + j];
}
}
}
  • 方案一 kernel 设置

  • 方案一 综合结果


  • 方案一 HLS log 文件

  • 方案一 Performance图





方案二(修改gmem位宽为64bit)

  • 方案二 kernel 设置

  • 为何为64bit?
    首先,源码中对外层loop_1进行pipeline,因此对于内层的loop_2自动进行uroll展开。内层for循环的边界为J_CNT = 2因此将loop_2 代码
    展开为如下代码形式。因此需要对ab进行两次读取,对a请求一次读入,对b请求一次读入。一拍只能对gmem请求一次,因为是并行执行因此a有两个数据a[i * J_CNT + 0]a[i * J_CNT + 1]需要读入所以接口位宽为sizeof(int) * 2
    对于代码来说gmem位宽应该等于sizeof(int) * J_CNT
1
2
3
4
5
// loop_2 uroll 展开过程
arrayA[i * J_CNT + 0] = a[i * J_CNT + 0];
arrayA[i * J_CNT + 1] = a[i * J_CNT + 1];
arrayB[i * J_CNT + 0] = b[i * J_CNT + 0];
arrayB[i * J_CNT + 1] = b[i * J_CNT + 1];
  • 方案二 综合结果


  • 方案二 HLS log文件

  • 方案二 Performance图





方案二 II = 2 原因分析

  • 修改源码,解决gmem ii = 2 问题(屏蔽掉b向arrayB赋值)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

#define J_CNT 2
#define I_CNT 4
#define BUFFER_SIZE I_CNT*J_CNT

kernel __attribute__((reqd_work_group_size(1, 1, 1)))
void vector_add(__global int* c,
__global const int* a,
__global const int* b,
const int n_elements)

{
local int arrayA[BUFFER_SIZE];
local int arrayB[BUFFER_SIZE];
local int arrayC[BUFFER_SIZE];

__attribute__((xcl_pipeline_loop))
loop_1:
for(int i = 0; i < I_CNT; i++) {
//__attribute__((xcl_pipeline_loop))
loop_2:
for(int j = 0; j < J_CNT; j++) {
arrayA[i * J_CNT + j] = a[i * J_CNT + j];
// arrayB[i * J_CNT + j] = b[i * J_CNT + j];
c[i * J_CNT + j] = arrayA[i * J_CNT + j];//+arrayB[i * J_CNT + j];
}
}
}
  • 综合结果分析

  • HLS log文件

  • Performence图



  • 为何源程序 II = 2?

该问题还要从loop_2 unroll说起

1
2
3
4
5
// loop_2 uroll 展开过程
arrayA[i * J_CNT + 0] = a[i * J_CNT + 0];
arrayA[i * J_CNT + 1] = a[i * J_CNT + 1];
arrayB[i * J_CNT + 0] = b[i * J_CNT + 0];
arrayB[i * J_CNT + 1] = b[i * J_CNT + 1];

关键原因是一拍只能对gmem请求一次 因为是并行执行因此第一拍对gmem进行a的读请求,a有两个数据a[i * J_CNT + 0]a[i * J_CNT + 1]需要读入,同理下一拍对gmem进行b的请求,b有两个数据b[i * J_CNT + 0]b[i * J_CNT + 1]需要读入。
因此解决办法也不言而喻:一个gmem只能一次读请求,但是综合多个gmem便可以在一拍内分别进行请求!

方案三(解决 II = 2问题)

  • 方案三 源码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#define J_CNT       2
#define I_CNT 4
#define BUFFER_SIZE I_CNT*J_CNT

kernel __attribute__((reqd_work_group_size(1, 1, 1)))
void vector_add(__global int* c,
__global const int* a,
__global const int* b,
const int n_elements)

{
local int arrayA[BUFFER_SIZE];
local int arrayB[BUFFER_SIZE];
local int arrayC[BUFFER_SIZE];

__attribute__((xcl_pipeline_loop))
loop_1:
for(int i = 0; i < I_CNT; i++) {
//__attribute__((xcl_pipeline_loop))
loop_2:
for(int j = 0; j < J_CNT; j++) {
arrayA[i * J_CNT + j] = a[i * J_CNT + j];
arrayB[i * J_CNT + j] = b[i * J_CNT + j];
c[i * J_CNT + j] = arrayA[i * J_CNT + j]+arrayB[i * J_CNT + j];
}
}
}
  • 方案三 设置

  • 方案三 综合结果




  • 方案三 HLS log文件
  • 方案三 Performence图


总结

  • 产生gmem carry dependency的两种解决办法:
    • 进行 gmem位宽大小的调整 32bit – 512bit
    • 多个 __global 参数采用多个gmem进行数据传输(max memory ports)

TODO 采用pipe传输(做一个memRead)

  • 问题(当数据为非32bit的倍数-512bit)
-------------本文结束 感谢您的阅读-------------
0%