OneAPI Intel实验矩阵乘法的实现

OneAPI简介

oneAPI是一个跨平台、开放和统一的编程模型,旨在简化并加速异构计算。它由英特尔公司领导,得到了众多合作伙伴的支持。oneAPI的目标是提供一个统一的编程环境,使开发者能够在不同的硬件架构上编写高性能应用程序,包括CPU、GPU、FPGA和其他加速器。

oneAPI采用了基于标准的编程模型,并提供了多种编程语言的支持,包括C++、DPC++(Data Parallel C++)和SYCL(Single-source C++ Heterogeneous Language)。这些语言提供了丰富的并行编程功能,使开发者能够有效地利用不同类型的硬件来加速应用程序。

oneAPI的核心是它的编程模型和一组开放的API(应用程序接口),这些API涵盖了各种计算领域,包括数据并行、图形计算、机器学习和深度学习等。通过使用这些API,开发者可以利用硬件的并行计算能力,提高应用程序的性能和效率。

除了编程模型和API,oneAPI还提供了一系列工具和库,用于优化和调试应用程序。这些工具和库可以帮助开发者分析和优化代码,以充分利用硬件的潜力。

总之,oneAPI是一个跨平台的编程模型,旨在简化和加速异构计算。它提供了统一的编程环境和一组开放的API,使开发者能够有效地利用不同类型的硬件来加速应用程序。通过使用oneAPI,开发者可以更轻松地编写高性能的并行应用程序,并充分发挥硬件的计算能力。

实验用到的软硬件环境

使用英特尔oneAPI Developer Cloud 服务,直接利用Developer Cloud平台中的CPU与GPU硬件

实验任务

  • 登录SYCL编程环境:
    1.进入oneapi/essential  2.进入02-SYCL-Program-Structure
  • 技术栈
     
  • 缓冲区(Buffer):
    在OneAPI中,缓冲区是一种用于存储数据的容器。它提供了一种抽象的视图,允许您在内存中创建、管理和访问数据。缓冲区可以是本地的、全局的或分布式的,具体取决于您的使用场景。使用缓冲区,您可以执行各种操作,如读取、写入、复制和映射数据。
  • 队列(Queue):
    队列是一种数据结构,用于存储待处理的任务或消息。在OneAPI中,队列用于管理异步操作和并行任务。您可以使用队列来提交作业、添加事件或发送消息。队列提供了一种高效的机制,用于在多个处理器之间协调工作,并确保任务按照特定的顺序执行。
  • 处理器句柄(Handler):
    处理器句柄是一个对象,用于表示特定的处理器或计算资源。在OneAPI中,处理器句柄提供了一种抽象的表示,用于访问和执行并行操作。通过处理器句柄,您可以配置计算环境、分配工作负载、执行并行算法等。处理器句柄还提供了一种机制,用于管理和监控处理器的状态和性能。
  • 访问器(Accessor):
    访问器是一种用于访问和操作数据对象的工具。在OneAPI中,访问器提供了一组函数和操作符,用于对缓冲区和数组进行读写操作。访问器支持各种数据类型,包括整数、浮点数、结构体等。通过使用访问器,您可以轻松地读取和修改数据对象的元素,而无需关心底层的内存布局和数据布局。

任务二:

按以下步骤写一个SYCL程序来实现包含两个矩阵的矩阵乘法运算: 

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

通过比较主机上的预设结果来验证你的程序是否正确。 

代码解释:

part1:

直接使用实例代码中给的CustomDiviceSeclector,它通过评估设备的类型和供应商来为设备分配一个评级。评级规则如下:

如果设备是GPU并且供应商名称包含指定的供应商名称(在构造函数中传入),评级为3。

如果设备是GPU但供应商名称不包含指定的供应商名称,评级为2。

如果设备是CPU,评级为1。

class CustomDeviceSelector {
 public:
  CustomDeviceSelector(std::string vendorName) : vendorName_(vendorName){};
  int operator()(const device &dev) {
    int device_rating = 0;
    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_;
};

part2:创建三个float类型的1024*1024的二维数组,使用随机数函数给matrix1和matrix2赋初始值,值的范围在0到2之间

 

//二维数组
    vector<vector<float>> matrix1(N, vector<float>(N));
    vector<vector<float>> matrix2(N, vector<float>(N));
    vector<vector<float>> matrix3(N, vector<float>(N));

    //初始化
    random_device rd;
    mt19937 rng(rd());
    uniform_int_distribution<int> dist(0, 2); 

    for (size_t i = 0; i < N; i++) {
        for (size_t j = 0; j < N; j++) {
            matrix1[i][j] = dist(rng);
            matrix2[i][j] = dist(rng);
        }
    }
    

part3:创建缓冲区用于主机和设备之间传输数据,定义字符串vender_name用于选定设备设备供应商名,创建三个accessor,使用h.parallel_for并行地遍历二维范围{N, N},其中每个工作项块大小为{64, 64}。在每个工作项中进行以下操作:1.获取全局ID,并将其用作矩阵索引。  2.在循环中计算矩阵相乘的结果并将其存储在输出矩阵M3中。

//Create buffers
    buffer<float, 2> Matrix1_buffer(reinterpret_cast<float*>(matrix1.data()), range<2>(N, N));
    buffer<float, 2> Matrix2_buffer(reinterpret_cast<float*>(matrix2.data()), range<2>(N, N));
    buffer<float, 2> Output_buffer(reinterpret_cast<float*>(matrix3.data()), range<2>(N, N));

queue q(selector);
    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}, {64, 64}), [=](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];
          }
        });
    });

源代码

任务二

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

using namespace sycl;
using namespace std;

class CustomDeviceSelector {
 public:
  CustomDeviceSelector(std::string vendorName) : vendorName_(vendorName){};
  int operator()(const device &dev) {
    int device_rating = 0;
    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;
    
    //二维数组
    vector<vector<float>> matrix1(N, vector<float>(N));
    vector<vector<float>> matrix2(N, vector<float>(N));
    vector<vector<float>> matrix3(N, vector<float>(N));

    //初始化
    random_device rd;
    mt19937 rng(rd());
    uniform_int_distribution<int> dist(0, 2); 

    for (size_t i = 0; i < N; i++) {
        for (size_t j = 0; j < N; j++) {
            matrix1[i][j] = dist(rng);
            matrix2[i][j] = dist(rng);
        }
    }
    
    cout<<"\nmatrix1:\n";
     for (size_t i=0;i<N;i++)
{
    for (size_t j=0;j<N;j++){
        cout<<matrix1[i][j]<<" ";
    }
    cout<<"\n";
}
    cout<<"\nmatrix2:\n";
     for (size_t i=0;i<N;i++)
{
    for (size_t j=0;j<N;j++){
        cout<<matrix2[i][j]<<" ";
    }
    cout<<"\n";
}
    //Create buffers
    buffer<float, 2> Matrix1_buffer(reinterpret_cast<float*>(matrix1.data()), range<2>(N, N));
    buffer<float, 2> Matrix2_buffer(reinterpret_cast<float*>(matrix2.data()), range<2>(N, N));
    buffer<float, 2> Output_buffer(reinterpret_cast<float*>(matrix3.data()), range<2>(N, N));


    //Choose device
    std::string vendor_name = "Intel";
    CustomDeviceSelector selector(vendor_name);
    

    //Submit task to multiply matrices
    queue q(selector);
    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}, {64, 64}), [=](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 accessor
    host_accessor h_a(Output_buffer, read_write);

    
    cout << "\nmatrix3:\n";
    for (size_t i = 0; i < N; i++) {
        for (size_t j = 0; j < N; j++) {
            cout << matrix3[i][j] << " ";
        }
        cout << "\n";
    }
    return 0;
}

任务三:统一共享内存在主机和设备(GPU)之间提供了一个统一的存储模式。修改你的程序以使用统一共享内存来实现内存分配和数据转换,从而替代缓存和存储。如果使用不同种类的USM将获得加分。重写代码以使用统一共享内存来实现内存分配和数据转换(如需要),照常运行Kernel来验证程序运行结果。(类似任务二,加入buffer的构造函数属性{property::buffer::use_host_ptr{}}来创建一个使用统一共享内存的缓冲区。)

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

using namespace sycl;
using namespace std;

class CustomDeviceSelector {
 public:
  CustomDeviceSelector(std::string vendorName) : vendorName_(vendorName){};
  int operator()(const device &dev) {
    int device_rating = 0;
    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;
    
    //二维数组
    vector<vector<float>> matrix1(N, vector<float>(N));
    vector<vector<float>> matrix2(N, vector<float>(N));
    vector<vector<float>> matrix3(N, vector<float>(N));

    //初始化
    random_device rd;
    mt19937 rng(rd());
    uniform_int_distribution<int> dist(0, 2); 

    for (size_t i = 0; i < N; i++) {
        for (size_t j = 0; j < N; j++) {
            matrix1[i][j] = dist(rng);
            matrix2[i][j] = dist(rng);
        }
}
cout<<"matrix1:\n";
for (size_t i=0;i<N;i++){
for(size_t j=0;j<N;j++){
cout<<matrix1[i][j]<<" ";
}
    cout<<"\n";
}
    //Create buffers
    buffer<float, 2> Matrix1_buffer(reinterpret_cast<float*>(matrix1.data()), range<2>(N, N));
    buffer<float, 2> Matrix2_buffer(reinterpret_cast<float*>(matrix2.data()), range<2>(N, N));
    buffer<float, 2> Output_buffer(reinterpret_cast<float*>(matrix3.data()), range<2>(N, N), {property::buffer::use_host_ptr{}});

    //Choose device
    std::string vendor_name = "Intel";
    CustomDeviceSelector selector(vendor_name);
    
    //Submit task to multiply matrices
    queue q(selector);
    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
    host_accessor h_a(Output_buffer, read_only);
    cout << "\nmatrix3:\n";
    for (size_t i = 0; i < N; i++) {
        for (size_t j = 0; j < N; j++) {
            cout << matrix3[i][j] << " ";
        }
        cout << "\n";
    }
    return 0;
}

任务四:分析运行时间,任务一22sec,任务三24sec

运行结果

实验反思

(1)注意使用nd-range分工作组时必须保证工作组的大小合适,在实验过程中,开始时设置工作组大小为64*64,出现nd-range错误,实际SYCL允许的工作组范围最大为512,因此修改大小为16*16,同时需要注意的是工作组的大小要可以被N整除

(2)要注意N过大,输出受限制的问题

(3)通过实验了解了主机和设备协调工作完成并行计算的基本概念

(4)面临的问题和挑战:针对N过大的问题,难以调整Notebook向IOpub发送数据过快的问题,以至于输出1024*1024的矩阵时出现了问题;针对nd_range的问题,修改工作组的大小出现问题,对于N的不同大小应该合理设置工作组的大小,否则运行出现错误;针对出现的段错误,同样可能是nd_range工作组的大小设置问题。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值