一文细说OpenCL框架

news2025/1/11 12:59:06

说明:

  • 子曾经曰过:不懂Middleware的系统软件工程师,不是一个好码农;

1. 介绍

  • OpenCL(Open Computing Language,开放计算语言):从软件视角看,它是用于异构平台编程的框架;从规范视角看,它是异构并行计算的行业标准,由Khronos Group来维护;
  • 异构平台包括了CPU、GPU、FPGA、DSP,以及最近几年流行的各类AI加速器等;
  • OpenCL包含两部分:

1)用于编写运行在OpenCL device上的kernels的语言(基于C99);

2)OpenCL API,至于Runtime的实现交由各个厂家,比如Intel发布的opencl_runtime_16.1.2_x64_rh_6.4.0.37.tgz

以人工智能场景为例来理解一下,假如在某个AI芯片上跑人脸识别应用,CPU擅长控制,AI processor擅长计算,软件的flow就可以进行拆分,用CPU来负责控制视频流输入输出前后处理,AI processor来完成深度学习模型运算完成识别,这就是一个典型的异构处理场景,如果该AI芯片的SDK支持OpenCL,那么上层的软件就可以基于OpenCL进行开发了。

话不多说,看看OpenCL的架构吧。

 

 资料直通车:Linux内核源码技术学习路线+视频教程内核源码

学习直通车:Linux内核源码内存调优文件系统进程管理设备驱动/网络协议栈

2. OpenCL架构

OpenCL架构,可以从平台模型、内存模型、执行模型、编程模型四个角度来展开。

2.1 Platform Model

平台模型:硬件拓扑关系的抽象描述

  • 平台模型由一个Host连接一个或多个OpenCL Devices组成;
  • OpenCL Device,可以划分成一个或多个计算单元Compute Unit(CU);
  • CU可以进一步划分成一个或多个处理单元Processing Unit(PE),最终的计算由PE来完成;
  • OpenCL应用程序分成两部分:host代码和device kernel代码,其中Host运行host代码,并将kernel代码以命令的方式提交到OpenCL devices,由OpenCL device来运行kernel代码;

2.2 Execution Model

执行模型:Host如何利用OpenCL Device的计算资源完成高效的计算处理过程

Context

OpenCL的Execution Model由两个不同的执行单元定义:1)运行在OpenCL设备上的kernel;2)运行在Host上的Host program;其中,OpenCL使用Context代表kernel的执行环境:

Context包含以下资源:

  • Devices:一个或多个OpenCL设备;
  • Kernel Objects:OpenCL Device的执行函数及相关的参数值,通常定义在cl文件中;
  • Program Objects:实现kernel的源代码和可执行程序,每个program可以包含多个kernel;
  • Memory Objects:Host和OpenCL设备可见的变量,kernel执行时对其进行操作;

NDrange

  • kernel是Execution Model的核心,放置在设备上执行,当kernel执行前,需要创建一个索引空间NDRange(一维/二维/三维);
  • 执行kernel实例的称为work-item,work-item组织成work-group,work-group组织成NDRange,最终将NDRange映射到OpenCL Device的计算单元上;

有两种方式来找到work-item:

  1. 通过work-item的全局索引;
  2. 先查找到所在work-group的索引号,再根据局部索引号确定;

以一维为例:

  • 上图中总共有四个work-group,每个work-group包含四个work-item,所以local_size的大小为4,而local_id都是从0开始重新计数;
  • global_size代表总体的大小,也就是16个work-item,而global_id则是从0开始计数;

以二维为例:

  • 二维的计算方式与一维类似,也是结合global和local的size,可以得出global_id和local_id的大小,细节不表了;

三维的方式也类似,略去。

2.3 Memory Model

内存模型:Host和OpenCL Device怎么来看待数据

OpenCL的内存模型中,包含以下几类类型的内存:

  • Host memory:Host端的内存,只能由Host直接访问;
  • Global Memory:设备内存,可以由Host和OpenCL Device访问,允许Host的读写操作,也允许OpenCL Device中PE读写,Host负责该内存中Buffer的分配和释放;
  • Constant Global Memory:设备内存,允许Host进行读写操作,而设备只能进行读操作,用于传输常量数据;
  • Local Memory:单个CU中的本地内存,Host看不到该区域并无法对其操作,该区域允许内部的PE进行读写操作,也可以用于PE之间的共享,需要注意同步和并发问题;
  • Private Memory:PE的私有内存,Host与PE之间都无法看到该区域;

2.4 Programming Model

  • 在编程模型中,有两部分代码需要编写:一部分是Host端,一部分是OpenCL Device端;
  • 编程过程中,核心是要维护一个Context,代表了整个Kernel执行的环境;
  • 从cl源代码中创建Program对象并编译,在运行时创建Kernel对象以及内存对象,设置好相关的参数和输入之后,就可以将Kernel送入到队列中执行,也就是Launch kernel的流程;
  • 最终等待运算结束,获取计算结果即可;

3. 编程流程

  • 上图为一个OpenCL应用开发涉及的基本过程;

下边来一个实际的代码测试跑跑,Talk is cheap, show me the code!

4. 示例代码

  • 测试环境:Ubuntu16.04,安装Intel CPU OpenCL SDK(opencl_runtime_16.1.2_x64_rh_6.4.0.37.tgz);
  • 为了简化流程,示例代码都不做容错处理,仅保留关键的操作;
  • 整个代码的功能是完成向量的加法操作;

4.1 Host端程序

#include <iostream>
#include <fstream>
#include <sstream>

#include <CL/cl.h>

const int DATA_SIZE = 10;

int main(void)
{
    /* 1. get platform & device information */
    cl_uint num_platforms;
    cl_platform_id first_platform_id;
    clGetPlatformIDs(1, &first_platform_id, &num_platforms);


    /* 2. create context */
    cl_int err_num;
    cl_context context = nullptr;
    cl_context_properties context_prop[] = {
        CL_CONTEXT_PLATFORM,
        (cl_context_properties)first_platform_id,
        0
    };
    context = clCreateContextFromType(context_prop, CL_DEVICE_TYPE_CPU, nullptr, nullptr, &err_num);


    /* 3. create command queue */
    cl_command_queue command_queue;
    cl_device_id *devices;
    size_t device_buffer_size = -1;

    clGetContextInfo(context, CL_CONTEXT_DEVICES, 0, nullptr, &device_buffer_size);
    devices = new cl_device_id[device_buffer_size / sizeof(cl_device_id)];
    clGetContextInfo(context, CL_CONTEXT_DEVICES, device_buffer_size, devices, nullptr);
    command_queue = clCreateCommandQueueWithProperties(context, devices[0], nullptr, nullptr);
    delete [] devices;


    /* 4. create program */
    std::ifstream kernel_file("vector_add.cl", std::ios::in);
    std::ostringstream oss;

    oss << kernel_file.rdbuf();
    std::string srcStdStr = oss.str();
    const char *srcStr = srcStdStr.c_str();
    cl_program program;
    program = clCreateProgramWithSource(context, 1, (const char **)&srcStr, nullptr, nullptr);


    /* 5. build program */
    clBuildProgram(program, 0, nullptr, nullptr, nullptr, nullptr);


    /* 6. create kernel */
    cl_kernel kernel;
    kernel = clCreateKernel(program, "vector_add", nullptr);


    /* 7. set input data && create memory object */
    float output[DATA_SIZE];
    float input_x[DATA_SIZE];
    float input_y[DATA_SIZE];
    for (int i = 0; i < DATA_SIZE; i++) {
        input_x[i] = (float)i;
        input_y[i] = (float)(2 * i);
    }

    cl_mem mem_object_x;
    cl_mem mem_object_y;
    cl_mem mem_object_output;
    mem_object_x = clCreateBuffer(context, CL_MEM_READ_ONLY | CL_MEM_COPY_HOST_PTR, sizeof(float) * DATA_SIZE, input_x, nullptr);
    mem_object_y = clCreateBuffer(context, CL_MEM_READ_ONLY | CL_MEM_COPY_HOST_PTR, sizeof(float) * DATA_SIZE, input_y, nullptr);
    mem_object_output = clCreateBuffer(context, CL_MEM_READ_WRITE, sizeof(float) * DATA_SIZE, nullptr, nullptr);


    /* 8. set kernel argument */
    clSetKernelArg(kernel, 0, sizeof(cl_mem), &mem_object_x);
    clSetKernelArg(kernel, 1, sizeof(cl_mem), &mem_object_y);
    clSetKernelArg(kernel, 2, sizeof(cl_mem), &mem_object_output);


    /* 9. send kernel to execute */
    size_t globalWorkSize[1] = {DATA_SIZE};
    size_t localWorkSize[1] = {1};
    clEnqueueNDRangeKernel(command_queue, kernel, 1, nullptr, globalWorkSize, localWorkSize, 0, nullptr, nullptr);


    /* 10. read data from output */
    clEnqueueReadBuffer(command_queue, mem_object_output, CL_TRUE, 0, DATA_SIZE * sizeof(float), output, 0, nullptr, nullptr);
    for (int i = 0; i < DATA_SIZE; i++) {
        std::cout << output[i] << " ";
    }
    std::cout << std::endl;


    /* 11. clean up */
    clRetainMemObject(mem_object_x);
    clRetainMemObject(mem_object_y);
    clRetainMemObject(mem_object_output);
    clReleaseCommandQueue(command_queue);
    clReleaseKernel(kernel);
    clReleaseProgram(program);
    clReleaseContext(context);

    return 0;
}

4.2 OpenCL Kernel函数

  • 在Host程序中,创建program对象时会去读取kernel的源代码,本示例源代码位于:vector_add.cl文件中

内容如下:

__kernel void vector_add(__global const float *input_x,
 __global const float *input_y,
 __global float *output)
{
 int gid = get_global_id(0);
 
 output[gid] = input_x[gid] + input_y[gid];
}

4.3 输出

 

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/193970.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

华为OD测试岗面经,一周走完面试流程

一周走完面试流程&#xff0c;10.18 机考&#xff0c;机试210 第一题:【最大N个数与最小N个数的和】 第二题&#xff1a;拼接URL 第三题&#xff1a;跳格子 性格测试:题目比较多&#xff0c;有一百多道&#xff0c;在三个选项中选出一个最符合的和一个最不符合的。答题的时候以…

C++:string模拟实现(下)

目录 一.引言 二.string类的容量操作接口 三.string类的字符串修改操作接口 1.两个插入字符的重载函数&#xff1a; 2.在string字符串末尾追加内容的接口 3.在指定位置pos删除n个字符的接口 四.string类字符串的字符和子串查找接口 五.全局定义的string类字符串比较运算…

SSM项目-小说网站

目录 设计目标 需求分析 网站主页 用户注册 1、需求分析 2、数据库设计 3、生成验证码 4、数据加密 1、MD5 2、BCrypt加密 5、数据交换格式 用户登录 找回密码 新用户注册 邮件发送 检测登录状态 书架功能 查看书架 添加书籍进入书架 删除书架上的书籍 获…

kafka在zookeeper中存储结构

1、存储结构图 2、ZooKeeper命令 ZooKeeper -server host:port cmd args stat path [watch] set path data [version] ls path [watch] delquota [-n|-b] path ls2 path [watch] setAcl path acl setquota -n|-b val…

Verticle-align

1.verticle-align的官方解释及所产生的疑问 1.1 vertical-align的官方解释 vertical-align会影响 行内块级元素 在一个 行盒 中垂直方向的位置 【这里有重点词汇&#xff0c;一个行盒&#xff0c;行内块元素&#xff0c;为什么不包括块元素呢&#xff0c;因为块元素是独占一行…

TOP10:餐饮店设计排行榜(2023年最新排名)

随着我国经济不断的高速发展&#xff0c;自13年以来&#xff0c;大众化餐饮市场呈现良好发展趋势&#xff0c;已由13年的2.64万亿增长到3.96万亿&#xff0c;增长率为10.7%&#xff0c;预计2017年到2022年增长速度为9.9%&#xff0c;达到6.28万亿。其中中餐主题餐饮占比维持在8…

JVM 基础 - Java 类加载机制

Java 类加载机制类加载器的分类类加载机制类加载器的分类 如果有必要&#xff0c;我们还可以加入自定义的类加载器。因为JVM自带的ClassLoader只是懂得从本地文件系统加载标准的java class文件&#xff0c;因此如果编写了自己的ClassLoader&#xff0c;便可以做到如下几点&…

django-rest-framework框架总结之View视图之APIView、GenericAPIView、视图集ViewSet

APIView APIView 是 REST framework 提供的所有视图的基类&#xff0c;继承自Django的View父类。支持认证、限流、授权等功能。 rest_framework.views.APIViewAPIView 与 View 的不同之处在于&#xff1a; 传入到视图方法中的是 REST framework 的 Request 对象&#xff0c;…

Linux gcc和gdb的使用

gcc/g编译器的使用 gcc如何使用 语法&#xff1a; gcc [选项] 编译文件 功能&#xff1a; 用于编译C语言程序&#xff0c;编译C程序使用g。 选项&#xff1a; 指令说明-E只激活预处理,这个不生成文件,你需要把它重定向到一个输出文件里面-S编译到汇编语言不进行汇编和链接…

python设计模式-单例模式,工厂模式

单例模式 单例模式将类的实例化限制为一个对象。 它是一种创建模式&#xff0c;只涉及创建方法和指定对象的一个类。 它提供了创建实例的全局访问点。 如何实现一个单例类&#xff1f; 下面的程序演示了单例类的实现&#xff0c;并多次打印创建的实例。 class Singleton:_…

动态规划(详细解释)

日升时奋斗&#xff0c;日落时自省 目录 1、Fibonacci 2、字符串分割 3、三角矩阵 4、路径总数 5、最小路径和 6、背包问题 7、回文串分割 8、编辑距离 9、不同子序列 10、总结 DP定义&#xff1a; 动态规划是分治思想的延伸&#xff0c;通俗一点来说就是大事化小&a…

高密度 ARM 服务器如何引领“数智时代”发展,打通“智变质变”正循环

并行计算 | 多样性计算 | ARM架构 深度学习 | 高性能计算 | ARM服务器 如今随着算力、高性能计算的快速发展&#xff0c;数字经济已经成为全球经济增长的主引擎。数字经济的快速发展&#xff0c;使得深度学习、数据分析、数据挖掘等技术迅猛发展起来。伴随国家政策东数西算的…

无痕埋点在Android中的实现

无痕埋点在Android中的实现 目标 解决手动打点效率低下问题自动化埋点 本篇技术实现主要是运行是代理&#xff0c;不涉及到插桩技术&#xff0c;不引入插件&#xff0c;对业务影响点最小 技术难点 1. 如何拦截到所有的view的点击事件 view有个setAccessibilityDelegate方…

Day02-带你走进数据分析的世界

文章目录Day02-带你走进数据分析的世界数据分析正在影响我们的工作、生活数据分析和你想象中的一样吗我们应该具备的数据分析能力Day02-带你走进数据分析的世界 数据分析正在影响我们的工作、生活 随着全球经济数字化转型的发展&#xff0c;各行各业都积累了大量的数据。 具有…

微信小程序做全局登录弹窗

需求&#xff1a;在任意需要弹出登录的页面&#xff0c;后台返回需要登录状态码&#xff0c;弹出登录弹窗进行登录&#xff0c;并刷新当前页面 过程&#xff1a;因为微信小程序无法封装一个全局组件通过方法全局调用。因此只能封装一个公共组件&#xff0c;在需要弹窗的页面注册…

Spark入门指南

文章目录什么是SparkSpark学习路线Spark入门指南什么是Spark Apache Spark 是一个开源集群运算框架&#xff0c;最初是由加州大学伯克利分校 AMP 实验室所开发。相对于 Hadoop 的 MapReduce 会在运行完工作后将中间数据存放到磁盘中&#xff0c;Spark 使用了存储器内存运算技术…

SpringMVC之请求与响应

目录 一&#xff1a;设置请求映射路径 1. 环境准备 二&#xff1a;问题分析 三&#xff1a;设置映射路径 四&#xff1a;请求参数 一&#xff1a;设置请求映射路径 1. 环境准备 创建一个Web的Maven项目 pom.xml添加Spring依赖 <?xml version"1.0" encodi…

基于Android的电子影院系统

需求信息&#xff1a; 客户端&#xff1a; 1&#xff1a;用户注册登录&#xff1a;通过手机号码、用户名称以及密码完成用户的注册和登录 2&#xff1a;影院信息&#xff1a;用户可以查看发布的影院信息以及查看影院具体反映的电影信息以及可以查看电影的宣传片&#xff1b; 3&…

Linux - Linux命令大全

阅读前可参考 https://blog.csdn.net/MinggeQingchun/article/details/128547426 一、Linux系统管理 &#xff08;一&#xff09;查看Linux系统版本 1、查看Linux内核版本 1、cat /proc/version&#xff1a;Linux查看当前操作系统版本信息 2、uname -a&#xff1a;Linux查看…

STM32--SPI、I2C、CAND等常用通信外设总线概括

1. SPI SPI是串行外设接口&#xff08; Serial Peripheral Interface&#xff09;的缩写。 SPI&#xff0c;是一种高速的&#xff08;之前做学传输比特115200 112k, 而SPI传输速度为10Mbps&#xff09;&#xff0c;全双工&#xff0c;同步的通信总线&#xff0c;并且在芯片的管…