基于oneAPI的SYCL异构计算在图像卷积中的应用

引言

​ 在图像卷积处理方面,oneAPI和SYCL的结合展现出了巨大的潜力。首先,oneAPI的跨平台特性使得它能够在各种计算设备上运行,这为图像处理提供了极大的灵活性。同时,SYCL则提供了一种高效且简洁的并行计算模型,使得开发者能够轻松地将算法并行化,从而在各种计算设备上获得更好的性能。

​ 在具体实现案例方面,我们考虑了一个使用在某个具体图像上,使用某个卷积核进行卷积。在这个例子中,我们首先使用传统算法编写了一个图像卷积的逻辑,然后使用SYCL对模型中的卷积部分进行了并行化处理。通过这种方式,我们成功地在各种计算设备上实现了高效的图像卷积处理,并且获得了良好的性能提升。

​ 在未来的工作中,我们计划进一步探索oneAPI和SYCL在图像处理中的应用。例如,我们可以考虑使用oneAPI和SYCL来加速更复杂的图像处理任务,如目标检测、语义分割等。同时,我们也可以探索如何使用oneAPI和SYCL来处理其他类型的机器学习任务,如自然语言处理、语音识别等。

图像卷积概览

  • 图像卷积的原理和重要性,图像卷积是一种通过将一个小的“卷积核”或“滤波器”滑动遍历图像来对图像进行处理的方法。这个卷积核通常是一个小的矩阵,其中每个元素代表一个像素与其相邻像素的权重。通过将这个卷积核在图像上滑动,并对每个位置的像素进行加权和,可以得到一个新的图像,这个图像是原始图像经过卷积处理后的结果。

  • 在数学原理方面,图像卷积可以看作是一种线性运算,它将卷积核与图像进行内积运算。具体来说,对于一个大小为 M×N 的图像和一个大小为 m×n 的卷积核,卷积运算可以用以下公式表示:

    ( f ∗ g ) ( x , y ) = ∑ i = 0 M − 1 ∑ j = 0 N − 1 f ( i , j ) g ( x − i , y − j ) (f*g)(x,y)=\sum_{i=0}^{M-1}\sum_{j=0}^{N-1}f(i,j)g(x-i,y-j) (fg)(x,y)=i=0M1j=0N1f(i,j)g(xi,yj)

    其中 f f f g g g 分别表示原始图像和卷积核, ( x , y ) (x,y) (x,y) 表示卷积核在图像上的位置, ( i , j ) (i,j) (i,j) 表示卷积核的起始位置, ∑ \sum 表示对所有像素进行加权和。

  • 在现代图像处理技术中,图像卷积被广泛应用于各种任务,如特征提取、边缘检测、图像增强、图像分割等。例如,在边缘检测中,可以使用卷积核对图像进行卷积运算,使得边缘部分的像素值发生变化,从而检测出边缘的位置。此外,在深度学习中,卷积神经网络(CNN)也广泛应用,其中卷积层就是通过卷积核实现对输入图像的特征提取和分类。总之,图像卷积是一种非常有效的图像处理方法,它可以实现对图像的快速特征提取和分类。随着计算机技术和人工智能技术的不断发展,图像卷积在未来仍将具有广泛的应用前景和研究价值。

  • 传统方法与异构计算方法的比较: 对比传统图像卷积算法和使用oneAPI和SYCL实现的异构计算方法,展示异构计算方法在性能和效率上的优势。

  • 实现细节: 介绍如何使用SYCL实现图像卷积,并在多个处理单元上并行执行卷积操作。

  • 实验设计与性能评估

    • 实验设置: 设计实验以比较传统卷积和基于SYCL的加速卷积在不同图像和卷积核尺寸下的性能。
    • 性能分析: 基于实验结果,分析并展示基于oneAPI和SYCL的方法在处理大规模图像卷积时的性能提升。

代码与运行结果

传统代码

​ 这段代码是用于执行图像卷积的C++程序。首先,它定义了一个从文件中加载图像和滤波器数据的函数。然后,它实现了一个卷积函数,该函数对给定的图像和滤波器进行卷积处理以生成新的图像数据。主函数中,代码从文件中加载图像和滤波器数据,然后调用卷积函数处理这些数据。最后,它计算并输出整个卷积处理过程的执行时间。程序设计上包含了基本的错误检查和性能测量功能。

%%writefile lab/convolution_classic.cpp
//==============================================================
// Copyright © Intel Corporation
//
// SPDX-License-Identifier: MIT
// =============================================================

#include <array>
#include <iostream>
#include <fstream>
#include <sstream>
#include <string>
#include <chrono>

constexpr int image_size = 100;
constexpr int filter_size = 5;

// 从文件中加载图像数据的函数
template <size_t N>
std::array<float, N*N> loadImageFromFile(const std::string& filename) {
    std::array<float, N*N> data = {};
    std::ifstream file(filename);

    if (!file) {
        std::cerr << "无法打开文件: " << filename << std::endl;
        return data;
    }

    std::string line;
    for (int i = 0; i < N*N && std::getline(file, line); ++i) {
        std::stringstream ss(line);
        if (!(ss >> data[i])) {
            std::cerr << "文件格式错误: " << filename << std::endl;
            return {};
        }
    }

    return data;
}

// 执行卷积操作的函数
std::array<float, image_size*image_size> applyConvolution(
    const std::array<float, image_size*image_size>& image,
    const std::array<float, filter_size*filter_size>& filter) {

    std::array<float, image_size*image_size> result = {};
    for (int x = 0; x < image_size; ++x) {
        for (int y = 0; y < image_size; ++y) {
            float sum = 0;
            for (int i = -filter_size/2; i <= filter_size/2; ++i) {
                for (int j = -filter_size/2; j <= filter_size/2; ++j) {
                    int ix = x + i, iy = y + j;
                    if (ix >= 0 && ix < image_size && iy >= 0 && iy < image_size) {
                        sum += image[ix*image_size + iy] * filter[(i+filter_size/2)*filter_size + (j+filter_size/2)];
                    }
                }
            }
            result[x*image_size + y] = sum;
        }
    }
    return result;
}

int main() {
    auto image = loadImageFromFile<image_size>("./data/image.txt");
    auto filter = loadImageFromFile<filter_size>("./data/filter.txt");
  
  	auto start_time = std::chrono::high_resolution_clock::now();

    if (image.empty() || filter.empty()) {
        std::cerr << "加载图像或滤波器失败" << std::endl;
        return 1;
    }

    
    // 循环1000次,方便看出结果
  	int total_iterations = 1;
  	for (int i = 0; i < total_iterations; ++i) {
        auto result = applyConvolution(image, filter);
    }
  
    auto end_time = std::chrono::high_resolution_clock::now();

    // 计算总时间
    auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(end_time - start_time);

    // 打印结果
    std::cout << "运行时间: " << duration.count() << " 毫秒" << std::endl;


    return 0;
}

基于oneAPI的SYCL加速卷积

​ 这段代码是一个使用SYCL(一种C++的并行编程标准)的C++程序,用于对图像进行卷积处理。程序首先定义了从文件中加载图像数据的函数。然后,它初始化图像和滤波器数据,以及一个用于存放结果的数组。

​ 程序的核心部分使用SYCL框架创建了一个队列(sycl::queue),用于提交并行计算任务。它创建了三个缓冲区(sycl::buffer),分别用于存储图像、滤波器和结果的数据。

​ 程序将一个并行任务(cgh.parallel_for)提交给队列,该任务为每个图像像素点执行卷积计算。在这个计算过程中,它根据滤波器的大小遍历图像的每个像素点及其周围的像素点,应用卷积公式计算新的像素值。

​ 异常处理部分(catch块)用于捕获并报告SYCL相关的异常,这有助于调试和稳定性。

​ 最后,程序计算并输出整个卷积过程的运行时间,用于性能分析。整体上,这个程序演示了如何利用SYCL进行数据并行处理,特别是在图像处理领域的应用。

%%writefile lab/simple.cpp
//==============================================================
// Copyright © Intel Corporation
//
// SPDX-License-Identifier: MIT
// =============================================================

#include <CL/sycl.hpp>
#include <array>
#include <iostream>
#include <chrono>
#include <fstream>
#include <sstream>

constexpr int image_size = 100;
constexpr int filter_size = 5;

// 从文件中加载图像数据
template <size_t N>
std::array<float, N*N> loadImageFromFile(const std::string& filename) {
  std::ifstream file(filename);
  std::array<float, N*N> data = { 0 };

  if (!file) {
    std::cerr << "无法打开文件: " << filename << std::endl;
    throw std::runtime_error("File not found");
  }

  std::string line;
  for (int i = 0; i < N*N; ++i) {
    if (!(std::getline(file, line) && (std::stringstream(line) >> data[i]))) {
      throw std::runtime_error("Error reading file");
    }
  }

  return data;
}

// 执行图像卷积
void performConvolution(const std::array<float, image_size*image_size>& image, 
                        const std::array<float, filter_size*filter_size>& filter, 
                        std::array<float, image_size*image_size>& result) {
  try {
    sycl::queue myQueue;

    sycl::buffer<float, 1> image_buffer(image.data(), image.size());
    sycl::buffer<float, 1> filter_buffer(filter.data(), filter.size());
    sycl::buffer<float, 1> result_buffer(result.data(), result.size());

    myQueue.submit([&](sycl::handler& cgh) {
      auto image_acc = image_buffer.get_access<sycl::access::mode::read>(cgh);
      auto filter_acc = filter_buffer.get_access<sycl::access::mode::read>(cgh);
      auto result_acc = result_buffer.get_access<sycl::access::mode::write>(cgh);

      cgh.parallel_for(sycl::range<2>(image_size, image_size), [=](sycl::id<2> idx) {
        int x = idx[0];
        int y = idx[1];
        float sum = 0;
        for (int i = -filter_size/2; i <= filter_size/2; ++i) {
          for (int j = -filter_size/2; j <= filter_size/2; ++j) {
            int ix = x + i, iy = y + j;
            if (ix >= 0 && ix < image_size && iy >= 0 && iy < image_size) {
              sum += image_acc[ix*image_size + iy] * filter_acc[(i+filter_size/2)*filter_size + (j+filter_size/2)];
            }
          }
        }
        result_acc[x*image_size + y] = sum;
      });
    });

    myQueue.wait();
  } catch (const sycl::exception& e) {
    std::cerr << "SYCL exception: " << e.what() << std::endl;
    throw;
  }
}

int main() {
  try {
    auto image = loadImageFromFile<image_size>("./data/image.txt");
    auto filter = loadImageFromFile<filter_size>("./data/filter.txt");
    std::array<float, image_size*image_size> result = {0};

    auto start = std::chrono::high_resolution_clock::now();
    performConvolution(image, filter, result);
    auto end = std::chrono::high_resolution_clock::now();
  
    auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(end - start);
    std::cout << "运行时间: " << duration.count() << " 毫秒" << std::endl;

  } catch (const std::exception& e) {
    std::cerr << "Exception: " << e.what() << std::endl;
    return 1;
  }

  return 0;
}

实验结果

100的image,5的卷积核

  • classical

image-20231201232606372

  • oneAPI
    image-20231201231312633

1000的image,5的卷积核

  • classical

image-20231201232521860

  • oneAPI

image-20231201232644156

1000的image,50的卷积核

  • classic
    image-20231201233211931

  • oneAPI

image-20231201233239767

实验结论

Image&Filter SizeClassic(ms)oneAPI(ms)
Image: 100, Filter: 571897
Image: 200, Filter: 5181764
Image: 200, Filter: 509821986
Image: 1000, Filter: 53091835
Image: 1000, Filter: 50305133448

实验结果记录,提供了在两个不同的系统(Classic和oneAPI)上处理不同图像大小和滤波器数量的执行时间数据。根据这些数据,我们可以进行以下分析和得出一些结论:

  1. 执行时间随图像大小的变化

    • Classic:随着图像大小的增加,执行时间明显增加。例如,当滤波器数量为5时,图像大小从100增加到1000,执行时间从7ms增加到309ms。这表明Classic系统在处理更大的图像时需要更多时间。
    • oneAPI:在图像大小增加时,执行时间也增加,但增幅不如Classic系统那么大。例如,当滤波器数量为5时,图像大小从100增加到1000,执行时间从1897ms增加到1835ms。
  2. 执行时间随滤波器数量的变化

    • Classic:滤波器数量的增加显著增加了执行时间,尤其是在较大的图像上。例如,当图像大小为1000时,滤波器数量从5增加到50,执行时间从309ms增加到30513ms。这表明在Classic系统中,处理更多滤波器的任务时,性能受到了显著影响。
    • oneAPI:当增加滤波器数量时,执行时间有所增加,但增加的幅度相对较小。例如,当图像大小为1000时,滤波器数量从5增加到50,执行时间从1835ms增加到3448ms。这表明oneAPI在处理更多滤波器时更为有效。
  3. 结论:
    Classic 系统在处理小图像和少量滤波器时表现良好,但在处理大图像或大量滤波器时性能急剧下降。这可能是因为Classic系统在处理大量并行任务时存在限制。
    oneAPI 系统在处理大图像和大量滤波器时表现更为出色,表明它可能具有更强的并行处理能力和更优的资源管理。

综上所述,选择Classic还是oneAPI应取决于具体的应用场景。对于小型或简单的图像处理任务,Classic可能更合适,而对于需要大量并行处理的复杂任务,oneAPI可能是更佳选择。

结论

​ 本文对小矩阵卷积的代码运行时间进行了研究,发现未使用SYCL库而是采用传统嵌套循环方式实现卷积操作,使得代码运行时间更短。相比于使用SYCL进行并行计算,传统的串行计算方式在小规模的问题上可能更加高效。这是因为在没有使用SYCL库提供的并行计算功能的情况下,每个像素点的卷积操作都是串行执行的,因此整个计算过程是按顺序进行的。相比之下,前面的代码使用了SYCL库,通过将数据和计算任务提交到SYCL队列中,借助设备的并行计算能力,可以同时处理多个像素点的卷积操作。这样可以大大提高计算速度,尤其是对于大规模的问题。

​ 在计算密集型的任务中,使用SYCL进行并行计算通常能获得更好的性能。但在小规模问题上,传统的串行计算方式可能更加高效,因为在启动并行计算所需的开销上,串行计算通常更少。

​ 本文验证了oneAPI和SYCL在图像卷积加速方面的有效性,展现了异构计算技术在现代图像处理中的应用潜力。这一发现为未来更复杂的图像处理和计算机视觉任务提供了新的解决方案。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值