一文说清OpenCL框架

背景

  • Read the fucking official documents! –By 鲁迅
  • A picture is worth a thousand words. –By 高尔基

说明:

  • 对不起,我竟然用了一个夺人眼球的标题;
  • 我会尽量从一个程序员的角度来阐述 OpenCL,目标是浅显易懂,如果没有达到这个效果,就当我没说这话;
  • 子曾经曰过:不懂 Middleware的系统软件工程师,不是一个好码农;

  • 介绍

一文说清OpenCL框架
  • 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的架构吧。

  1. OpenCL架构

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

2.1 Platform Model

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

一文说清OpenCL框架
  • 平台模型由一个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的执行环境:

一文说清OpenCL框架

Context包含以下资源:

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

NDrange

一文说清OpenCL框架
  • 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的索引号,再根据局部索引号确定;

以一维为例:

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

以二维为例:

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

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

2.3 Memory Model

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

一文说清OpenCL框架

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

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

  • 编程流程

一文说清OpenCL框架
  • 上图为一个OpenCL应用开发涉及的基本过程;

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

  1. 示例代码

  2. 测试环境:Ubuntu16.04,安装Intel CPU OpenCL SDK( opencl_runtime_16.1.2_x64_rh_6.4.0.37.tgz);

  3. 为了简化流程,示例代码都不做容错处理,仅保留关键的操作;
  4. 整个代码的功能是完成向量的加法操作;

4.1 Host端程序

#include
#include
#include

#include

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 输出

一文说清OpenCL框架

参考

The OpenCL Specification

欢迎关注公众号,不定期分享技术文章

一文说清OpenCL框架

Original: https://www.cnblogs.com/LoyenWang/p/15085664.html
Author: LoyenWang
Title: 一文说清OpenCL框架

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/585931/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

  • 写shell,运行出错:syntax error near unexpected token `$’dor”

    【解决过程】 1.网上搜了下,参考:linux shell脚本报错:syntax error near unexpected token </a></p> …

    Linux 2023年5月28日
    0121
  • USB转双串口产品设计-RS485串口

    基于USB转2路串口芯片CH342,可以为各类主机扩展出2个独立的串口。CH342芯片支持使用操作系统内置的CDC串口驱动,也支持使用厂商提供的VCP串口驱动程序,可支持Windo…

    Linux 2023年6月7日
    0137
  • redis 基本信息查询

    在客户端可以用telnet命令 telnet ip port 再输入info 返回如下信息: 每个参数的含义: redis_version redis版本gcc_version g…

    Linux 2023年5月28日
    0131
  • CentOS单机安装k8s并部署.NET 6程序 压测 记录

    前面部分依照CentOS单机安装k8s并部署.NET 6程序来进行,内存、cpu、centos版本一致,之前222元买的三年8M 2c4g 腾讯云轻量服务器,这个教程算是写的很详细…

    Linux 2023年6月7日
    0134
  • FusionCompute制作Linux虚拟机模板

    创建虚拟机 创建虚拟机下一步这里实验就创建红帽7.4选择存储磁盘精简创建先在存储上传红帽镜像挂载镜像VNC登录安装安装完成配置yum源安装bzip*(因为最小化安装没有bzip程序…

    Linux 2023年6月8日
    0201
  • 配置免密登陆服务器

    前言 原来自己学习的时候在阿里云买自己的学习机,一台主机自己瞎折腾。但是参加工作以后管理的主机越来越多了,上服务器看的频率也越来越频繁,虽然有时候shell管理工具可以很方便的保存…

    Linux 2023年6月14日
    0134
  • HTS-一键启动

    #!/bin/bash #program:runall and checkall process #author:sundz 20220606 v1 如果此用户不存在这个,则配置为…

    Linux 2023年6月7日
    0143
  • 高等代数:3 线性方程组的解集的结构

    3 线性方程组的解集的结构 1、定义1:数域K上所有n元有序数组组成的集合(K^{n}),连同定义在它上面的加法运算和数量乘法运算,以及满足的8条运算法则一起,称为数域K上的一个 …

    Linux 2023年6月8日
    0138
  • Django Model 如何返回空的 QuerySet

    >>> from django.contrib.auth.models import User >>> User.objects.none() …

    Linux 2023年6月7日
    0132
  • Lvs

    Lvs Lvs Lvs简介 体系结构 LVS管理工具 配置 lvs-nat 模式的 httpd 负载集群—http 配置lvs-nat模式的httpd负载集群&#821…

    Linux 2023年6月6日
    0154
  • 小程序字节转GBK及UTF8

    前段时间在Android原生搞的BLE扫码枪又要移植到小程序上来。本以为小程序不支持BLE的,结果一搜,还真支持-_-|| 。 蓝牙部分问题不大,遇到的主要问题是, 小程序环境如何…

    Linux 2023年6月13日
    0241
  • 2021 — 冰与火之歌

    大半夜的睡不着,越发的想给 2021 年作个总结,一想更睡不着了,来唠叨下吧。 一拖再拖的婚礼。本来定在今年正月的婚礼,因为疫情,不敢办,村里也不让办;然后选在国庆吧,卧槽,封城了…

    Linux 2023年6月16日
    0161
  • 4.5 Linux压缩文件或目录中文件为.gz格式(gzip命令)

    gzip 是 Linux 系统中经常用来对文件进行压缩和解压缩的命令,通过此命令压缩得到的新文件,其扩展名通常标记为”.gz”。 再强调一下,gzip 命令…

    Linux 2023年6月7日
    0138
  • zabbix模板,角色,用户,权限管理

    用户管理 用户组 用户角色 用户 模板管理 模板组 模板 posted @2022-09-07 22:22 溜溜威 阅读(14 ) 评论() 编辑 Original: https:…

    Linux 2023年6月7日
    0131
  • 正则表达式在grep中的使用

    GREP用法 grep "after" profile #查找文件内的包含after单词的行 grep -n "after" profile…

    Linux 2023年6月11日
    0136
  • WEB安全信息收集

    每次做测试都要去网上找信息太费劲这里放了常用的所有工具和网站,后期有更新在改。 子域名&敏感信息 通过大量的信息收集,对目标进行全方位了解,从薄弱点入手。 利用Google…

    Linux 2023年6月7日
    0121
亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球