跳到主要内容

HIP异构编程模型

1. 基本概念

HIP编程方法是在类GPU加速器DCU上运行高性能的并行计算。DCU加速卡上使用的编程模型为AMD公司开发的ROCm 编程模型。HIP编程模型是ROCm编程模型的一个扩展,它允许开发人员使用C/C++语言编写代码,并使用HIP库来调用ROCm库。

它是一种显式并行编程模型,和CUDA编程是类似的(就像它们都有核函数)。

文档:https://rocm.docs.amd.com/projects/HIP/en/docs-6.0.0/developer_guide/build.html

在CPU上运行的部分称为主机端(host),在DCU加速器上云霄的部分称为设备端(device)。主机端代码是CPU代码,代码在CPU上运行,入口函数是main。而设备端就是指加速器设备,代码对应在加速器上运行。代码采用扩展的C语法(HIP_C),设备端代码组织成核函数运行。

HIP使用Runtime API来在主机端分配设备显存,管理主机端和设备端的内存拷贝,运行设备端核函数等。

设备端代码由HIP_C构成并运行在DCU加速器上,被称为核函数(Kernel)。

2. HIP异构程序实现流程(CPU+DCU)

  1. 将需要在设备端进行运算的数据从主机端内存传输至设备内存。(hipMalloc)

  2. 启动核函数对已传输至DCU上的数据进行运算等操作。

  3. 将运算结果从设备端传输回主机端内存。

3. HIP模型线程组织模式

HIP异构编程核函数执行模型由thread-block-grid三层结构组成。

线程是并行程序的基本构执行单元,每个核函数所启动的线程都有一个唯一的线程ID,通过HIP内置的hipthreadldx进行访问。这个变量是一个三分量矢量,在x,y,z方向上分别命名为hipthreadldx_x,hipthreadldx_y,hipthreadldx_z。

对于block与grid拥有同样的内置变量,从而确定线程在全局中的偏移量(线程局部存储(Thread Local Storage, TLS)中的数据在进程的全局内存空间中的位置偏移)。

4. HIPPROF工具

hipprof是DTK提供的性能分析工具,用于对HIP应用程序提供可视化的性能分析。提供的功能有:单进程、多进程、多节点的HIP API跟踪,ROCTX跟踪,MPI日志解析,PMC硬件计数器性能数据的统计输出,以及相关的辅助功能。

hipprof 指令的基本使用格式是:

hipprof [options] <app command line>

示例:

hipprof --hip-trace ./testhi

hipprof命令行参数

常用卷积算法

常用卷积算法有4种:

直接卷积计算

卷积计算公式:

y(i,j)=m=(M1)/2M/2n=(N1)/2N/2x(i+m,j+n)h(m,n)y(i,j)= \sum_{m=-(M-1)/2}^{M/2} \sum_{n=-(N-1)/2}^{N/2} x(i+m,j+n) \cdot h(m,n)

按照卷积的计算特性直接进行计算。

卷积核中的权重矩阵在经过处理(补边)后的输入图像中滑动,每次在输入图像中会覆盖一个与权重矩阵大小一致的子矩阵与之进行对应元素的相乘并累加(点积运算)。

Winograd算法

通过特定的计算公式增加加法计算来减少乘法的计算次数达到优化目的。

Winograd算法的实现过程如下:

  1. 分解卷积运算:将卷积运算分解成多个较小的矩阵乘法和加法运算,从而减少实际的乘法次数。通常情况下,卷积运算涉及多个乘法和加法操作,通过算法分解后,可以减少这些操作的数量。

  2. 应用Winograd变换:通过应用Winograd变换,将输入数据和卷积核转换为一个新的域。这个新的域使得卷积运算转化为更少的乘法操作。例如,Winograd F(2, 3, 3)算法将2x3x3的卷积核转化为一组较小的矩阵,从而减少计算量。

  3. 执行变换和卷积:在变换后的域中进行卷积运算。由于在新域中卷积计算的乘法次数较少,这一步骤的计算复杂度会有所降低。

  4. 逆变换:将卷积结果从变换后的域映射回原始数据域。此步骤将变换后的卷积结果恢复到原始数据空间中,得到最终的卷积结果。

  5. 后处理:对逆变换后的结果进行必要的后处理步骤,以适应实际应用中的需求,例如调整边界效应或去除填充等。

#include <iostream>
#include <vector>
#include <cmath>

using namespace std;

// Matrix multiplication helper function
vector<vector<float>> matMult(const vector<vector<float>>& A, const vector<vector<float>>& B) {
int rowsA = A.size();
int colsA = A[0].size();
int colsB = B[0].size();

vector<vector<float>> C(rowsA, vector<float>(colsB, 0));

for (int i = 0; i < rowsA; ++i) {
for (int j = 0; j < colsB; ++j) {
for (int k = 0; k < colsA; ++k) {
C[i][j] += A[i][k] * B[k][j];
}
}
}

return C;
}

// Winograd F(2, 3, 3) convolution implementation
vector<vector<float>> winogradF23Convolution(const vector<vector<float>>& input, const vector<vector<float>>& kernel) {
// Define the Winograd transformation matrices
vector<vector<float>> G = {{1, 0, 0},
{0, 1, 0},
{0, 0, 1}};

vector<vector<float>> B = {{1, 1, 0},
{1, -1, 0},
{0, 0, 1}};

vector<vector<float>> B_inv = {{0.5, 0.5, 0},
{0.5, -0.5, 0},
{0, 0, 1}};

// Apply Winograd transformations
vector<vector<float>> A = matMult(B, input);
vector<vector<float>> K = matMult(B, kernel);

// Perform convolution in the transformed domain
vector<vector<float>> C = matMult(A, K);

// Apply inverse Winograd transformations
vector<vector<float>> output = matMult(C, B_inv);

return output;
}

int main() {
// Example input and kernel
vector<vector<float>> input = {{1, 2, 3},
{4, 5, 6},
{7, 8, 9}};

vector<vector<float>> kernel = {{1, 0, -1},
{0, 1, 0},
{1, 0, 1}};

// Perform Winograd convolution
vector<vector<float>> result = winogradF23Convolution(input, kernel);

// Print result
cout << "Result of Winograd F(2, 3, 3) convolution:" << endl;
for (const auto& row : result) {
for (float val : row) {
cout << val << " ";
}
cout << endl;
}

return 0;
}

FFT算法(快速傅里叶变换)

将输入图像和卷积核进行FFT变换,在频域中进行卷积,再进行IFFT变换,得到空域卷积结果。

并行快速傅里叶变换是斯坦福CS315b的一个课设题目。

CS315b:https://web.stanford.edu/class/cs315b/

FFT软件包:https://sjplimp.github.io//docs/fft/README.html#_cch3_931359462

#include <iostream>
#include <vector>
#include <complex>
#include <cmath>

// 使用标准复数类型
typedef std::complex<double> Complex;
typedef std::vector<Complex> CArray;// 存储复数数据的动态数组

// 基于Cooley-Tukey算法,适用于长度为2的幂次的序列
// 递归实现FFT
void fft(CArray &arr, bool invert) {
int n = arr.size();
if (n <= 1) return;

// 分割数组
CArray even(n / 2), odd(n / 2);
for (int i = 0; i < n / 2; ++i) {
even[i] = arr[i * 2];
odd[i] = arr[i * 2 + 1];
}

// 递归调用FFT
fft(even, invert);
fft(odd, invert);

// 合并结果
double angle = 2 * M_PI / n * (invert ? -1 : 1);
Complex w(1), wn(cos(angle), sin(angle));
for (int i = 0; i < n / 2; ++i) {
Complex t = w * odd[i];
Complex u = even[i];
arr[i] = u + t;
arr[i + n / 2] = u - t;
w *= wn;
}
}

int main() {
// 示例数据
CArray data = {1, 1, 1, 1};

// 打印原始数据
std::cout << "Input data:\n";
for (const auto &x : data) {
std::cout << x << " ";
}
std::cout << "\n";

// 执行FFT
fft(data, false);

// 打印FFT结果
std::cout << "FFT result:\n";
for (const auto &x : data) {
std::cout << x << " ";
}
std::cout << "\n";

return 0;
}

Im2col 算法

“Im2col” 是一个图像处理算法,用于将图像数据从原始的 2D 图像格式转换为适合某些深度学习运算(如卷积神经网络中的卷积操作)的格式。“Im2col” 是 “image to column” 的缩写。该算法将输入图像的局部区域(例如,卷积核大小对应的部分)展开为一列(或几列,具体取决于步长和填充),然后所有的局部区域会被同等地展开成一列或几列,这个过程可以大大简化工单化网络计算的复杂度。

简化了卷积操作。

Im2col 算法的主要步骤包括:

  1. 确定卷积核大小和步长:计算需要多少个局部区域,以及每个区域的大小。

  2. 填充(如果需要):在输入图像的边缘添加额外的零,以确保在边缘处卷积核也能完全覆盖。

  3. 逐块展开:将每个局部区域展开为一列。例如,如果有一个 3x3 的卷积核,并且步长为 1,输入图像大小为 5x5,不考虑填充,则会有 9 个局部区域。每个区域会被展开为一列,形成由 9 列组成的列向量。

  4. 合并列向量:将所有局部区域展开成的列向量合并,形成一个大矩阵。这个矩阵的大小就是:(输入图像的高 - 卷积核的高 + 1)X(输入图像的宽 - 卷积核的宽 + 1)X 卷积核的面积。

通用优化方法