Intel oneAPI——SYCL编程

Intel oneAPI 简介

Intel oneAPI是Intel提供的统一o编程模型和软件开发框架。 它旨在简化可充分利用英特尔各种硬件架构(包括 CPU、GPU 和 FPGA)的应用程序的开发。

oneAPI 提供了一组工具、库和框架o,使开发人员能够编写跨不同硬件平台的高性能代码。 它支持多种编程语言,包括 C++、Fortran 和数据并行 C++ (DPC++)。 借助 oneAPI,开发人员可以使用熟悉的编程模型编写代码并针对不同的硬件架构,而无需对代码进行重大修改。

推荐的软件硬件环境

使用oneAPI中支持SYCL编程模型的C++编译器 ,利用CPU作为主机(Host)同时利用GPU作为设备(Device)完成作业,可选利用CPU作为设备(Device)进行运行性能比对和讨论。

  推荐使用英特尔oneAPI Developer Cloud 服务,可免安装额外环境,直接利用Developer Cloud平台中的CPUGPU硬件。 相应注册及启用服务可参考相应的使用手册,关于在登录节点上运行的JupyterLab递交代码到计算节点的排队系统的相关操作方式及命令,可以参考任务递交相应命令参考 以及 队列管理参考

  如果使用自有硬件,推荐使用基于英特尔酷睿6代或更新版本的处理器与系统中同时可用的基于XeArc的集成显卡,通过完成与安装操作系统配套的相应oneAPI软件工具,通过构建自有开发环境方式。  相应的工具可参考oneAPI基础工具套件独立编译器的网页, 硬件要求可参考发布说明中的具体要求

  如果使用的自有硬件,同时包含有英特尔酷睿6代或更新版本的处理器以及NvidiaGPU,也可以尝试在安装参考oneAPI基础工具套件后,通过安装CodePlay提供的oneAPI支持Nvidia插件,参考安装指南进行环境搭建。

SYCL编程实战——矩阵乘法

要求

组建2个序列(向量)的浮点数,每个序列的规格是N(如N=1024*1024),构成矩阵输入值。用随机值初始化序列。使用缓存和存储来实现对设备(GPU)上矩阵内存的分配并运行。运行SYCL Kernel实现两个矩阵的并行运算,运用SYCL nd_range概念来定义Kernel的运行范围。使用SYCL排队系统来运行设备上的Kernel。 Kernel运行结束后,使用存储将结果从设备(GPU)检索回主机。 

使用Buffer-Accessor Memory Model

为了控制主机和设备之间的数据共享和传输,SYCL 提供了一个 buffer 类。这里使用二维的buffer,能更直观的展现矩阵相乘。

//创建三个vector,两个用来存输入矩阵,一个用来存输出矩阵
 std::vector<float> A(Size),B(Size),C(Size,0);

//随机数初始化A和B矩阵
  std::mt19937 rng(42);
  uniform_int_distribution<int> dist(0, 4); 
  for (size_t i = 0; i < Size; i++) {
      A[i] = dist(rng);
      B[i] = dist(rng);
    }
    
//创建二维buffers
  buffer<float, 2> Matrix1_buffer(A.data(), range<2>(N, N)),
  Matrix2_buffer(B.data(), range<2>(N, N)), Output_buffer(C.data(), range<2>(N, N));
    

接下来是创建队列。队列是一种将工作提交给设备的机制。

 queue q;
    begin=clock();
    q.submit([&](handler &h) {
      //创建与buffer相对应的accessor
      accessor M1 (Matrix1_buffer,h,read_only);
      accessor M2 (Matrix2_buffer,h,read_only);
      accessor M3 (Output_buffer,h,write_only);
       //执行并行运算
      h.parallel_for(nd_range<2>({N, N}, {16, 16}), [=](nd_item<2> item) {
          //# Multiplication
          size_t row = item.get_global_id(0);
          size_t col = item.get_global_id(1);
          for (size_t k = 0; k < N; ++k) {
              M3[row][col] += M1[row][k] * M2[k][col];
          }
        });
    });

在计算矩阵乘法需要用到二维的nd_range,具体原理如下图:

 最后还需要创建一个accessor来进行结果的读取

  //创建host accessor读取最终结果
    host_accessor h_a(Output_buffer,read_only);

完整代码如下:

%%writefile lab/vector_add.cpp
//==============================================================
// Copyright © Intel Corporation
//
// SPDX-License-Identifier: MIT
// =============================================================
#include <sycl/sycl.hpp>
#include <iostream>
#include <vector>
#include <random>
#include<ctime>

using namespace sycl;
using namespace std;

//# Reference of the former method to select the best device

class CustomDeviceSelector {
 public:
  CustomDeviceSelector(std::string vendorName) : vendorName_(vendorName){};
  int operator()(const device &dev) {
    int device_rating = 0;
    //We are querying for the custom device specific to a Vendor and if it is a GPU device we
    //are giving the highest rating as 3 . The second preference is given to any GPU device and the third preference is given to
    //CPU device. 
    if (dev.is_gpu() & (dev.get_info<info::device::name>().find(vendorName_) !=
                        std::string::npos))
      device_rating = 3;
    else if (dev.is_gpu())
      device_rating = 2;
    else if (dev.is_cpu())
      device_rating = 1;
    return device_rating;
  };

 private:
  std::string vendorName_;
};


int main() {
    const int N = 1024;
    const int Size = N * N;
    clock_t begin,end;
    double t;
    //# Create two vectors for the input matrices and one for the output
    std::vector<float> A(Size),B(Size),C(Size,0);
    
    //# Initialize matrices A and B with random values
    std::mt19937 rng(42);
    uniform_int_distribution<int> dist(0, 4); 
    //std::uniform_real_distribution<float> dist(0.0, 1.0);
    for (size_t i = 0; i < Size; i++) {
        A[i] = dist(rng);
        B[i] = dist(rng);
    }
    
    std::cout<<"\nInput Matrix1:\n";    
    for (size_t i = 0; i < N; i++) {
        for (size_t j = 0; j < N; j++) {
            std::cout << A[i*N+j] << " ";
        }
        std::cout << "\n";
    }
    std::cout<<"\nInput Matrix2:\n";    
    for (size_t i = 0; i < N; i++) {
        for (size_t j = 0; j < N; j++) {
            std::cout << B[i*N+j] << " ";
        }
        std::cout << "\n";
    }
    
    //# Create buffers
    buffer<float, 2> Matrix1_buffer(A.data(), range<2>(N, N)),
    Matrix2_buffer(B.data(), range<2>(N, N)), Output_buffer(C.data(), range<2>(N, N));
    
    //# Choose the best device
    
    // Pass in the name of the vendor for which the device you want to query
    std::string vendor_name = "Intel";
    // std::string vendor_name = "AMD";
    // std::string vendor_name = "Nvidia";
    CustomDeviceSelector selector(vendor_name);
    
    //# Submit task to multiply matrices
    queue q(selector);
    begin=clock();
    q.submit([&](handler &h) {
      //# Create accessors for buffers
      accessor M1 (Matrix1_buffer,h,read_only);
      accessor M2 (Matrix2_buffer,h,read_only);
      accessor M3 (Output_buffer,h,write_only);

      h.parallel_for(nd_range<2>({N, N}, {16, 16}), [=](nd_item<2> item) {
          //# Multiplication
          size_t row = item.get_global_id(0);
          size_t col = item.get_global_id(1);
          for (size_t k = 0; k < N; ++k) {
              M3[row][col] += M1[row][k] * M2[k][col];
          }
        });
    });
     
    //# Create a host accessor to copy data from device to host
    host_accessor h_a(Output_buffer,read_only);
end=clock();
    //# Print Output values 
    std::cout<<"\nOutput Values:\n";
    
    for (size_t i = 0; i < N; i++) {
        for (size_t j = 0; j < N; j++) {
            std::cout << C[i*N+j] << " ";
        }
        std::cout << "\n";
    }
    
   
     t=double(end-begin)/CLOCKS_PER_SEC;
    std::cout<<"RUN_TIME:"<<t<<"\n";
    return 0;
}

 由于源代码采用两个1024*1024的矩阵相乘不方便展示,此处展示将N改为4后(同时nd_range需要修改为nd_range<2>({N, N}, {2, 2})的运行结果,如下图:

使用Unified Shared Memory

相交于Buffer-Accessor Memory Model,Unified Shared Memory(简称USM)更为简洁,其原理是开辟一片CPU和GPU可以公共使用的内存区域。

这里使用malloc-shared开辟内存

 //使用malloc_shared开辟内存
    float *M1 = malloc_shared<float>(Size, q);
    float *M2 = malloc_shared<float>(Size, q);
    float *M3 = malloc_shared<float>(Size, q);
    
//初始化同Buffer-Accessor Memory Model

由于空间是线性的,程序中通过一维的方式来表示二维的矩阵(即下标为ij的元素在第i*N+j的位置上)。矩阵乘法的原理同上。

queue q;
q.parallel_for(nd_range<2>({N, N}, {16, 16}), [=](nd_item<2> item) {
      //# Multiplication
      size_t row = item.get_global_id(0);
      size_t col = item.get_global_id(1);
      for (size_t k = 0; k < N; ++k) {
          M3[row*N+col] += M1[row*N+k] * M2[k*N+col];
      }
    }).wait();

完整代码如下:

%%writefile lab/usm_lab.cpp
//==============================================================
// Copyright © Intel Corporation
//
// SPDX-License-Identifier: MIT
// =============================================================
#include <sycl/sycl.hpp>
#include <iostream>
#include <vector>
#include <random>
#include<ctime>
using namespace sycl;
using namespace std;

//# Reference of the former method to select the best device
class CustomDeviceSelector {
 public:
  CustomDeviceSelector(std::string vendorName) : vendorName_(vendorName){};
  int operator()(const device &dev) {
    int device_rating = 0;
    //We are querying for the custom device specific to a Vendor and if it is a GPU device we
    //are giving the highest rating as 3 . The second preference is given to any GPU device and the third preference is given to
    //CPU device. 
    if (dev.is_gpu() & (dev.get_info<info::device::name>().find(vendorName_) !=
                        std::string::npos))
      device_rating = 3;
    else if (dev.is_gpu())
      device_rating = 2;
    else if (dev.is_cpu())
      device_rating = 1;
    return device_rating;
  };

 private:
  std::string vendorName_;
};

int main() {
    const int N = 1024;
    const int Size = N * N;
    clock_t begin,end;
    double t;
    //# Choose the best device
    // Pass in the name of the vendor for which the device you want to query
    std::string vendor_name = "Intel";
    // std::string vendor_name = "AMD";
    // std::string vendor_name = "Nvidia";
    CustomDeviceSelector selector(vendor_name);
    
    //# Submit task to multiply matrices
    queue q(selector);
    std::cout << "Device : " << q.get_device().get_info<info::device::name>() << "\n";
    //# USM allocation using malloc_shared
    float *M1 = malloc_shared<float>(Size, q);
    float *M2 = malloc_shared<float>(Size, q);
    float *M3 = malloc_shared<float>(Size, q);
    
    //# Initialize matrices A and B with random values
    std::mt19937 rng(42);
    uniform_int_distribution<int> dist(0, 4); 
    //std::uniform_real_distribution<float> dist(0.0, 1.0);
    for (size_t i = 0; i < Size; i++) {
        M1[i] = dist(rng);
        M2[i] = dist(rng);
        M3[i]=0;
    }
    std::cout<<"\nInput Matrix1:\n";    
    for (size_t i = 0; i < N; i++) {
        for (size_t j = 0; j < N; j++) {
            std::cout << M1[i*N+j] << " ";
        }
        std::cout << "\n";
    }
    std::cout<<"\nInput Matrix2:\n";    
    for (size_t i = 0; i < N; i++) {
        for (size_t j = 0; j < N; j++) {
            std::cout << M2[i*N+j] << " ";
        }
        std::cout << "\n";
    }
    begin=clock();
    q.parallel_for(nd_range<2>({N, N}, {16, 16}), [=](nd_item<2> item) {
      //# Multiplication
      size_t row = item.get_global_id(0);
      size_t col = item.get_global_id(1);
      for (size_t k = 0; k < N; ++k) {
          M3[row*N+col] += M1[row*N+k] * M2[k*N+col];
      }
    }).wait();
    end=clock();
     t=double(end-begin)/CLOCKS_PER_SEC;

    //# Print Output values 
    std::cout<<"\nOutput Values:\n";
    for (size_t i = 0; i < N; i++) {
        for (size_t j = 0; j < N; j++) {
            std::cout << M3[i*N+j] << " ";
        }
        std::cout << "\n";
    }
    std::cout<<"RUN_TIME:"<<t<<"\n";
    free(M1, q);
    free(M2, q);
    free(M3, q);
    return 0;
}

由于源代码采用两个1024*1024的矩阵相乘不方便展示,此处展示讲N改为8后(同时nd_range需要修改为nd_range<2>({N, N}, {2, 2})的运行结果,如下图:

程序运行结果分析 

通过调用ctime库中的函数来测量程序的运行时间(已添加到上面给出的代码中)。两个程序使用的相同的随机数种子,以减小测量误差。

当矩阵大小为1024*1024时,两组程序的运行时间对比如下:

使用Buffer-Accessor Memory Model

使用Unified Shared Memory

 

由此可见,当数据运算量大时,使用USM能够提高运行速度。

心得体会

使用SYCL进行编程需要掌握一定的标准和框架。在实验中,我对SYCL的语法和编程模型有了更深入的了解。使用SYCL进行矩阵乘法的过程中,我意识到了异构计算平台的优势。能够在CPU和GPU等不同设备上并行执行代码,有效地利用硬件资源,提高整体性能。同时并行编程通常需要更加深入地理解硬件架构和并行计算的原理。在实验过程中,那些并行性、数据同步和错误处理相关的挑战,对于我日后的进行并行编程任务将会很有帮助。此外,矩阵乘法涉及大量的数据运算,因此对内存的优化显得尤为重要。本次实验让我学到了如何有效地管理数据的内存布局、传输以及缓存的使用,以提高程序的整体性能。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值