Intel® oneAPI 在龙蜥社区里面的支持情况分享|龙蜥大讲堂116期
内容分析
1. What is Intel® oneAPI
2. Intel® oneAPI infrastructure
3. Anolis Intel® oneAPI enabling status
4. Qwen2 demo
这是我今天想和大家分享的四部分内容。首先简单介绍一下什么是 oneAPI 。通过一个小程序给大家一个比较直观的认识,帮助大家理解 oneAPI 在代码层面是一个什么样子。然后我会介绍一下 oneAPI 的整个软件站以及这个软件站在龙蜥里面实例化的情况。然后我会给大家分享一下龙蜥里面 oneAPI enabling 的进展情况,在龙蜥里面 oneAPI 都做了哪些事情。最后,基于一台 Desktop 上面有一张 Intel 的显卡,我会一步一步的教大家怎么样 Set Up 起来一个 Qwen2 Demo 。 Demo 本来是要实时的在线演示,但是因为网络原因,可能我会截图或视频给大家分享。
01.What is Intel® oneAPI
1.1 Intel® oneAPI Specification
首先我们来看一下什么是 oneAPI 。大家都知道,在AI人工智能发展到今天这个阶段,算力的多样性是一个很明显的趋势。特别是在数据中心里面,通过整合和利用不同类型的计算资源,包括右下角图上的 CPU 、 GPU、 FPGA 、其他类型加速器,我们可以实现更加高效和灵活的计算,为我们的工作负载提供比较强大的硬件处理能力。
在面对不同类型的硬件资源的时候,通常开发者需要为不同的硬件平台编写不同的代码。使用的也是不同的编程语言。明显增加了开发的难度和维护的复杂度。这也是 Intel one API 需要解决的一个主要的问题。
oneAPI 提供一个开放的、统一的编程模型,使开发者能够使用单一的代码库和统一的编程接口来编写自己的应用程序,而不需要关心底层硬件的适配问题。同时 oneAPI 也提供各种性能库。这些库都对 Intel 的 CPU 和 GPU 进行了优化,使得代码在不同的硬件之间移植的时候也不需要做大量的源码级的修改去适应新的硬件的新特性。最后 oneAPI 还有一套比较完整的工具链支持跨平台的性能分析和调优。
这边是 oneAPI 7个核心组件。第一个是 oneDPL ,它是一个 DPL C++的一个标准库,它里面包含了 DPL C 的头文件和以些并发编程的模板库。
第二个 oneDNN 是一个深度学习的加速库,用于加速深度学习的推理和训练,在不同的 Intel 的平台上,根据 amx 512或者 amx 这些特性的不同也都做了相应的加速优化。
第三个 oneCCL 是一个跨平台的通信库,主要是用在这个分布式的训练和并行计算当中。
第四个 level zero 是一个统一的低级别硬件的抽象接口。开发者可以通过 level zero 的 API 可以直接访问和控制不同的硬件加速器。上层的这个应用就不需要关心底下到底是运行在什么设备上。
下面的 oneDAL 是一个跨平台的数据分析库。比如说比较经典的一些机器学习的算法,包括线性回归、逻辑回归、支持向量机这些都在库里面。如果大家用过 Python 的话,这些算法 Python 上也是有的。但是 one DAL 对硬件平台做了一些性能优化。
接下来 oneTBB wanted 是一个很基础的组件,它其实就是一个并行库。开发者如果用这个库可以利用到硬件上多核的资源来并行地运行自己的任务代码。
最后一个 oneMKL 是一个高性能的数学库,它提供了大量 Intel 的一些专利,经过 Intel 优化过的数据函数。比如说向量、矩阵、线性代数、快速复利变换都会放在这些库里。这些库就构成了一个比较完善的 oneTII 的环境,使得开发者可以在各种应用场景下面通过 oneTII 的组件来高效的利用到多样性算力的硬件能力。
如果大家有跟踪 oneTII 规范的话,其实在 1.3 的版本之前还有一个叫DPCL+的组件,它其实是维护了一套 Cko 的扩展,比如一些扩展的 API 。但是随着随着 DPCL+、oneAPI 的应用的增多,这些扩展现在都被集成到了 cko 的标准里面去了。所以 oneAPI 的规范里面就不需要再单独维护 DPCL+ 的扩展组件,所以现在的核心组件从原来的八个变成了七个。
下面有一个很简单的例子。这是一个非常简单的任务,简单解释一下,通过分配一块内存把这个内存填充一下,就是这么一个简单的任务。那么对于代码的逐行的解释式,首先我们会初始化一个队列,一个 Q ,我们会为这个 Q 选择一个设备。使用一个 Default 设备选择器,下面还有两行是 GPU 和 CPU 的选择器,我把它注释掉了,这个 Default 选择器意味着让程序自动的自己去选择一个最好的设备来运行,然后输出告诉给用户我选择的这个设备。然后他会用 Make Shared 去分配一块共享内存。用这个 Q 的两个操作,一个是 Parallel For ,一个是 Wait 来把这个内存给填充起来。最后,在这个 Host 上,它会对这片内存再进行一个输出验证,把这个开始分配的内存给释放掉,就是这样一个比较简单的应用程序。这个应用程序和以前的应用程序有个很不一样的地方,它是 CPU 和你的设备在这里是我的一块 GPU ,它是 CPU 和 GPU 混合运行的一个模式。
大家可以看一下下一页。
它的编译很简单,调用 oneAPI 的编译器对它进行 Score 的一个编译。可以看到左边这个应用程序其实是跑在我的 R70 的这个显卡上的,它填充的结果由我的 CPU 输出确认没有什么问题。右边的话,其实我的编译选项都是完全是一模一样的。大家可以看到就是右边这个 Demo 其实跑在我的 CPU 上。 同样的话它的输出也是准确无误的。
我简单解释一下应用程序,它主要分成两部分,一部分是在 Host上跑的 Code ,然后另外一个是在 Git 上跑的 Code 。
右边的这个代码块大家可以看到黄颜色其实是跑在 Git 上,这里面有几个关键的点。第一个就是这个程序是用 Data Parallel C 写的,它其实是标准的 C17 版本的一个扩展,用的是 LLVM 的编译器。关于设备的选择,如果你选择的是 Default 选择器,那么它其实对你现在系统上的所有设备进行一个打分,得分高的就会被它选成 Queue 设备去运行你的任务。
Q 的作用主要是把你的任务传递给设备,让它跑起来并且还有一些同步的机制。比如说这里面的 Queue 函数,同时这里面还有一个 USM 的这样的一个调用,它是基于指针的,所以他在 Post 上和 Device 都是可以访问的。
当然这个 Self 还有一些变动,比如说他还有 Mac OS 和 Mac device ,这样一些东西其实是需要你显式地去管理,就是 CPU 和 GPU 之间数据的迁移和访问。比如说大家可以看到其实在其他的架构里面,你可以看到很多显示的就是 Memory Copy 从 Host 上 Copy 到 Device 上,然后再从 Device 搬回来。
硬件价格的不同,有一些 Case 需要搬移,有一些不需要。在简单的例子里就是把它都隐藏掉,在 Host 和 Guest 上同时的例子,当然我们在实际的任务里面肯定不会只有这样简单的操作,比如说要做一个通用矩阵的乘法,比如像 Jiam 的操作,我们可以自己写公式把它放到 Device 上,比如说取代 Data I等于I这一行。我们也可以调用 oneAPI 里面的我右下角的这个框里面的内容,也可以直接去调用 oneAPI 库里面实现好的 API 通过传入队列,这个队列它其实就已经带了 Device 的参数,然后再传入一些矩阵、矩阵的参数,那么这个 API 就可以帮你在 Device 上把通用矩阵的乘法做好,并且把这个结果传回给 CPU 。
整个代码以及它的运行是怎么样做到在 Horse 上和 Device 上运行的?
我们可以看一下 DPC++ 这个应用程序的编译过程。还有一个Runtime 的过程,如果下次有机会的话,我可以给大家分享。这里主要介绍一下它编译的流程,简单来讲它的源代码通过解析后会被分成 Host 和 Device 两个部分。
Host 上是比较简单的,大家用 Gcc 去编译的话都是比较直接的,它是可以直接生成 Host 上的目标文件。那么 Device 上首先会通过编译器前端生成的 LM 、 VM 的 VR ,用于表示源代码编译后中间的代码,中间的代码就起到了连接前端的语言和后端的目标代码的一个桥梁作用,在生成的 LM VM 之后,因为我们的 device 是 GPU ,所以在 Middle end 里就会把 LM 的一个中间表达转换成 Sport We 的一个中间表达。这个 spot 其实也是一种中间的表示形式,它和 LM VM 是有点类似的,它不同的地方在于它是专门用于图形编程。在后端 GPU 也就是 InterGPU 的 graphics 的变形会再编译成 device 上的目标文件。
最后 Device 上的目标文件就还要再包一层。通过大家看到 Vaporobject File 把 Device 文件再包一层。通过链接器把 Host 上的目标文件和 Device 上的目标文件连到一起生成最后的可执行文件,也被称为 Fat Binary 。如果大家如果去用比如说像 Objdump 、 Redeoif 这样的工具读 Fat Binary 的时候可以看到它的 Section 有单独的 Clown Off Load 的 Section ,这个Section里面其实就是 Device 目标的 文件里面的内容。
02. Intel® oneAPI infrastructure
我讲一下 Intel oneAPI 的软件。
我们从上往下看的话,上面是我们的应用程序,比如说像大圆模型,如果是有 Pytorch 或者是 Testform 这样的框架,中间还提供 Apex Atex 这样的 Tection 。包括一些中间件,这些中间件里面都包含有 Intel Cpu 和机构的优化。如果是像 Llama.Cpp 这样的一些框架,就不需要这样的中间件。但是也可以有 Apex , Lm 这样的中间件来对它进行一些优化。再往下面其实就是 oneAPI 的核心部分。
这部分前面我其实做过了比较详细的介绍,除了这些核心的部分,还有一些工具,左边的这个 Porting Tools 帮助开发者把其他框架上的应用程序移植到 Onetection 上。右边的话是有一些性能分析、 Trace 、 Debug这样一些工具。
再到下面分为两大块, CPU 这一侧的 Host Interface 比较简单,因为它是一个直通的。其他的所有的异构设备都会通过一个叫 Level 接口——硬件上面的那层,相当于第零层,通过一套底层的抽象层把硬件的算力提供给上面的 Library 和应用程序使用。包括设备的枚举,设备内存的分配、释放、任务的提交、调度执行等等。这些设备的行为都是通过 Level Zero的下面一层叫 Compute Runtime 主要负责把 Level Zero API 收到的一些请求和 OS 上的设备驱动程序连接起来进行交互。
比如说,用这个设备的 Control 、用来做地址的 Mamry Map这样的请求。在 Compute Runtime 下面就是操作系统上的一些接口,比如说像 Self S 、Device S这样的一些接口。然后再到下面就是设备的驱动程序、操作系统和不同的架构的硬件。我这里用的是一块 Inte的显卡。 Upgrade,Linux Kernel 的版本在6.2以后,它是原生的支持了驱动,支持了 Graphics 的。
当我的 OS 在这个版本之上,我就可以就直接使用 OS 的 GPU driver ,不需要做其他的工作。所以从整个的光图来看,这个范畴内的就是从规范,到库,再到Tools,然后再到下面的Level的接口这整套软件还是比较完善的,它的成熟度也是比较高的。所以我们目前正在把这套软件落地到龙蜥里面去。想为龙蜥的异构计算的生态提供一个选项。新的异构设备的生产商,比如说一些国产的设备,如果你可以提供自己的 Backend ,通过 oneAPI 的统一的接口就可以把设备的算力提供给上面的应用程序。
下面我简单介绍一下 oneAPI 在龙蜥里面实例化的情况。左边这张图我们前面已经看过,右边这张图从下往上看,在硬件上我们是基于我们服务器的 CPU 以及我们 Class 的 CPU core 这两种类型处理器,然后 GPU 这一块我们现在是用了一块 Client 端的独立的显卡, GPR70 拥有16GB的显存。再往上的话我们采用的是 Anolis 23演绎的一个版本,加上 Linux kernel 6.6 的版本,因为 Anolis 23 是支持 Linux 6.6 的版本。
所以在这一块我们不需要进行 enabling 的工作。再往上面的话,包括 Level 的接口、 oneAPI 的组件这一块,我们在容器里面的实例化和 Redhea的版本是拉齐的。因为在 Redhead 那边它有一套完整的完整性测试的,所以我们在龙蜥里面也是借用了一下他的 Effort 。再到上面的话,我们有跑基于 Python 的叫 Fast Tack 的一个框架,可以用到 Patouch 以及 Apex 的中间件。另外的话我们也运行 Liama.Cpp ,把 Qwen2 成功地运行在了 Liama.Cpp 类的框架上。这个就是 oneAPI 在龙蜥里面的实例化情况。
03. Anolis Intel® oneAPI enabling status
oneAPI 在龙蜥里面落地的进展情况。
我们大概是去年底、今年初开始了 oneAPI 项目在龙蜥联络站的一个项目。首先我们在龙蜥里面创建了一个 Portal , Portal 的地址在下面,大家可以看它是一个代码仓库,当时呢我们把 oneAPI 所有的核心组件和需要的依赖都给梳理出来放在 Portal 里面。在今年年初的时候有跑通 Liama2 的 Demo ,在龙蜥的 Honest 23.0 的版本上。
同时把我们跑动的整个过程写成了一个文档也放到了仓库里。再往下走的话,我们分为三层, Kernel 和 GPU 这层我前面也做过详细的介绍,我们最开始是用6.2.9的版本,发现在 A770 的 GPU 驱动的上面已经运行得非常好。在第18周的时候又验证了一下6.6.25版本,在 Anolis 23.1 Redis出来之后第一时间对 Anolis 23.1 做了一个验证。所以这条线上的内容我们已经都完成了。
第二条线上的内容主要是比较多的软件包,这些软件包包括 Level Zero 和 User Mode 的 GPU package ,这些 Package 我们也都对龙蜥 Anolis 23 的分支做了回合和 Merge 的工作。在阿里同事的帮助下我们也通过 Analyst 测试的一些软件仓库,我们把这些包抓下来做了一些测试,确认它可以正常的工作。所以在 Anolis 23 官方的 Eport 软件仓库里面,我们的 Level Zero 和我们用户的包已经在它的仓库里面发布。大家可以直接用 Yum , Install 来安装。
最后一条线,因为我们目前这样的实力主要是用到了前面这几个库,包括 oneDNN 、 oneTBB 、 oneTML。这几个库我们也都做了 Mart 工作。后面包括 DBCL runtime 还有 oneCCL 和 oneDAL 这些库目前还在进展当中。大家可以看一下左上角的 Summary 就是我们在龙蜥里面 Create 大概十个 Repor 。我们有27次的 PR 提交。在龙蜥的 Anolis 23 的软件仓库里面 Release 出来17个包,还有两个包好像正在路上。同时我们也 Release 出来两个 Docker 的 Image 。
这两个 Docker 的 Image 第一个是 Basekit ,它和 oneAPI 的Basekit 是相对应的。这个 Docker Image 里面包含了 Intel 的 oneAPI basekit 和所有用户态的 GPU ,包括 Level Zero 、 Computer Runtime ,所以用户如果有应用程序需要从源码级的级别开始构建,这个源码包提供一个比较完整的 oneAPI 的构建环境。下面一个 Image ,大家可以看到这个 Image 的大小和上面那个是有明显的差别的,这个 Image 很小,如果你的应用程序在其他地方已经构建好了,那么你需要挪到 oneAPI 上来运行,它提供所有的 GPU 的包和你的应用程序的 Binary 需要的 Runtime 那些库,所以它只包含 Runtime 那些库。那么这两个和 Image 现在都已经发布到了龙蜥的 Dockhub 上,大家可以按照自己的需要下载使用,后期我们也会完善相关的文档,帮助大家快速的构建自己的应用程序。
04. Qwen2 demo
最后我想分享一下Demo。本来是要一个实时 Demo 的,因为网络的原因,我做了一些截图,应该也能看到效果。
首先我们需要一个 OS 。这个 OS 我这里选用的是23.1,我们在装好这个 OS 之后,你可以确认一下你的 OS 版本,如果是6.6的版本,那么这个 OS 它的 Graphics Driver 对于 Intel 的 CPU 和 Ark 的 GPU 都是 Ready 的,你可以通过 Lisgpi Get一条命令去确认你的显卡已经正确的安装和 Load 好I915的驱动。同时你也可以 Check 一下 Device S ,确认一下它下面是有内容的,因为这个接口比较重要,它是 Computer Runtime 这个库的基础,这个 Runtime 很多的操作都要通过 Device S 来和 OS 驱动进行交互,这是第一步。
那么第二步就是安装 CPU 用户态的 package 。首先我们把 Anolis 一套的软件仓库 Able 起来,安装大概十个左右的软件包,当然他会把一些依赖同时也给你装上了,因为这些软件包我们已经全部都贡献到了 Anolis 里面去了,所以你也不需要在其他的地方再找,相关的依赖所有的东西都在龙蜥里面为大家准备好了,你只需要装上就可以。
然后就是 oneAPI toolkits 。 oneAPI toolkits 现在还是在容器的外面,我们也在努力的往容器里面落地的进程,但是现在的话需要大家把 Intel 的 oneAPI 仓库使能起来,使能起来之后用一条命令就可以把 Intel 的 basis 安装好。所以,简单概括一下前面三步。第一步就是安装一个 OS ,第二部就是你从龙蜥的 Repo 里面把我们需要 GPU 的东西全部装好,第三步是从 Intel 的里面把所有 oneAPI 的东西都装号。这就可以了。
第四步我们再确认一下, Level Zero 就是 Intel 的环境有一个叫 CL device 这样的一条命令,就类似于前面那个 Ispci 一样。如果你确认一下 Sycl-Is 你会发现它有很多的设备,包括 Open CL 的设备, Level_Zero 的设备,下面我还有一块集成的显卡。所以这个独立的显卡 Level_Zero:Gpu:0
是 Demo 主要运行的 Dys ,如果能够看到这样一个 Gpu:0 Dys
,整个环境都已经 Stepup 。
下面是 run llama.cpp
框架,在 Intel GPU 上。我们简单解释一下这条命令,这个 Mean 其实就是 llama.cpp
的一个主程序,后面跟的是 Qwen2 的 Instruct 的模型。我对她做了四皮特的量化,后面跟的就是一些参数,包括你的模型输出内容的长度、你需要 Off Load 到 GPU 上的模型有多少层、你选择哪个设备、包括比如说 GPU 0。在最后一个 Chartl 是我要进入对话模式,它会输出很多Log ,我打我这里面截取了一部分 Log ,这个 Log 上面可以显示来看到总共有29层,29层都 Of Low 到了 GPU 上,那么这个模型其实和 Ngl33是对应的,如果大家把这个33改成13的话那么它其实有一部分的雷尔会放到 GPU 上。另外一部分放到 CPU 上跑。我展示的这个 Demo 其实是所有的类都放到了 GPU 上。所以简单的问一下你是谁,他会告诉你我是通信纤维。这个就是目前 Demo 的一个可以运行起来的流程。
然后下面的话本来是要做一个实时的演示的,但是因为网络原因,我截了几张图,这张图是用 CPU 跑这个模型,那么大家可以看到我在上面也 Cat 一下我 Script 的参数,他们并没有刚才 GPU 那样比较复杂的参数,你直接指定这个模型给他,告诉他我要进入对话模式,那么他就会在下面进行对话。在右边的两个框框里面,上面的框框其实是跑的 Astop ,他可以看到你所有的 CPU 的运行状态。因为这个模型是跑在 CPU 上的,所以大家可以看到有好多 CPU 都是有利用率。右边的下半部分是 GPU 的使用情况,是 Intel GPU 的一个小工具。大家可以看到,因为模型是跑在 CPU 上的,所以在 GPU 上就看不到任何的 Activity 。这是一张截图。
这张和刚才那张,如果我放快一点大家可以看到其实还是有明显的不一样。除了那个上面命令行参数之外,大家可以看到基本上右边的 CPU 只有 CPU 零和 CPU 2参与模型的这推理。它大量的数据处理和运算其实都在 GPU 上,包括2D的加速、 Bitter 、还有一些 Activity 。我录了一段屏,主要是想给大家感受一下速度,其实这个速度在 GPU 上的话完全可以接受,大家可以看一下。
现在他在硬盘上 Load 这个模型, Load 之后大家也可以关注一下右边的 CPU 和 GPU 的使用情况。这个是我手敲可能敲的比较慢一点。我让他在介绍 IntelI 的 oneAPI ,它输出的内容还是比较多比较详细的。从刚才这两个 Case 可以看到,主程序的 Binary 是不变的。 是同一个 Binary ,但是它既可以跑在 CPU 上,也可以在 GPU 上。 这个其实就是实现了设计的初衷,可以用一份相同的代码来跑在不同的异构的硬件算力上。以上就是我今天要分享的全部的内容。谢谢大家!