CANN训练营Ascend C算子开发基础阶段笔记--与CUDA的对比学习(上)
写在最前
由于工期紧张,加上已经有了很多较全的优质笔记,故我只节选一些我认为和CUDA强相关的知识点写在笔记里面。
基础概念
SIMD
SIMD,即单指令多数据计算:Simple Instruction Multi Data,一条指令可以处理多个数据
Ascend C的计算API都是SIMD样式。
并行计算
常见的两种方法:SPMD并行和流水线并行
SPMD并行计算原理:
1.启动一组进程,他们运行相同程序
2.将待处理数据切分,将切分后数据分片分发给不同进程处理
3.每个进程对自己的数据分片进行三个任务处理
流水线并行:
1.启动一组进程
2.对数据进行切分
3.每个进程都处理所有的数据切片,对输入数据分片只做一个任务的处理(类似操作系统中时间片轮转)
对比学习CUDA
CUDA 架构同样支持 SIMD 操作。在 CUDA 中,这通常通过其多线程并行执行模型实现,其中多个线程可以同时执行相同的指令,但处理不同的数据。这在 CUDA 中被称为 warp 级别的并行处理,每个 warp 包含 32 个线程,它们同时执行相同的指令。
然而,在讨论CUDA时,更应强调其SIMT特性;
SIMT (Single Instruction, Multiple Thread) in CUDA:
CUDA 实际上使用了 SIMT 架构,这是一种类似于 SIMD 但又有所不同的并行计算模型。在 SIMT 中,单条指令被多个线程同时执行,每个线程处理不同的数据。这种方式使得 CUDA 能够在 GPU 上有效地管理和执行大量线程,提供高度的并行性。
在 CUDA 中,一组线程(通常是 32 个线程,称为一个 warp)会同时执行相同的指令序列。尽管每个线程执行相同的指令,但它们可以独立地操作不同的数据元素,允许进行个性化的数据处理。
并行计算方法 in CUDA:
CUDA 的并行计算方法(包括 SPMD 和流水线并行)依然适用。但是,这些并行计算方法是在 SIMT 框架下实现的,意味着它们是通过多线程而不是仅仅多数据点来执行的。
Ascend C编程模型与范式
并行计算架构抽象
SPMD编程模型介绍
多层级API封装
1.计算类API,包括标量计算AP1向量计算API、矩阵计算API,分别实现调用Scalar计算单元、Vector计算单元、Cube计算单元执行计算的功能。
2.数据搬运API,上述计算API基于Loal Memor数据进行计算,所以数据需要先从Glbal Memory搬运至Local Memory,再使用计算接完成计算,最后从Local Memory搬出至Global Memory。执行搬运过程的接口称之为数据搬移接口,比如DataCopy接口
3.内存管理API,用于分配管理内存,比如AllocTensor、FreeTensor接口。
4.任务同步API,完成任务间的通信和同步,比如EnQue、DeQue接口。该类型API,开发者无需关注内部实现逻辑,使用简单的AP)接口即可完成。
对比学习CUDA
计算类 API in CUDA:
CUDA 提供了各种类型的计算 API,包括用于标量、向量和矩阵运算的函数。这些计算通常在 CUDA 核函数(kernels)中实现,它们利用了 CUDA 的多线程能力来执行并行计算。
在 CUDA 中,对应 Ascend C 中的 Scalar、Vector、Cube 计算单元,CUDA 使用了不同类型的内存访问和运算模式,比如共享内存(shared memory)、寄存器(registers)和全局内存(global memory)。
数据搬运 API in CUDA:
CUDA 中的数据搬运主要涉及全局内存(global memory)与本地内存(例如共享内存)之间的数据传输。CUDA 提供了各种内存复制函数,如 cudaMemcpy,用于在主机(CPU)和设备(GPU)之间以及设备内部的不同内存区域之间搬运数据。
内存管理 API in CUDA:
CUDA 提供了一系列用于内存分配和释放的 API,如 cudaMalloc、cudaFree 等。这些函数用于在 GPU 的全局内存中分配和释放空间。
任务同步 API in CUDA:
CUDA 中的任务同步主要通过流(streams)和事件(events)来实现。使用 cudaStreamSynchronize、cudaEventSynchronize 等 API 可以实现不同 CUDA 流中任务的同步。
CUDA 还提供了用于控制核函数执行和内存操作的队列机制,使得开发者可以有效地组织和同步 GPU 上的并行任务。
孪生调试
Ascend C提供挛生调试方法,即在cpu侧创建一个npu的模型并模拟它的计算行为,用来进行业务功能调试。相同的算子代码可以在cpu域调试精度,npu域调试性能。
CPU域调试
1、运行使能ASSERT,初步拦截算子指令或框架使用错误,如参数超限,指令数据地址重叠,该芯片不支持的
指令等2、可使用gdb单步调试算子计算精度,也可以在代码中直接编写printf(…)来观察数值的输出。
NPU域调试
2、可使用工具获取真实芯片上profiling数据,进行性能精细调优。
对比学习CUDA
CPU 域调试 in CUDA:
CUDA 提供了在 CPU 上进行调试的能力,主要通过 NVIDIA 的 Nsight Eclipse Edition 或 Visual Studio 插件。这些工具允许开发者在 CPU 上单步执行和调试 CUDA 代码。
CUDA 代码可以在主机(CPU)端编译和运行,以初步测试算法的正确性。虽然这不完全等同于在 CPU 上模拟 GPU 的行为,但它提供了一种检查和验证 CUDA 算子的方式。
使用类似 assert 的语句可以帮助初步拦截算子指令或框架使用中的错误。开发者也可以使用标准的调试技术(如断点、单步执行和打印语句)来调试代码。
NPU/GPU 域调试 in CUDA:
CUDA 提供了用于性能调优的工具,如 NVIDIA Visual Profiler 和 Nsight Compute。这些工具允许开发者收集和分析在 GPU 上执行的 CUDA 代码的性能数据。
这些工具可以帮助开发者识别性能瓶颈、优化内存访问模式和执行配置等。
常用数据定义
GlobalTensor
LocalTensor
对比学习CUDA
在 CUDA 中,全局内存和局部内存的概念也是存在的,不过通常以不同的术语出现:
Global Memory in CUDA:
在 CUDA 中,全局内存(Global Memory)是在所有线程和内核函数中都可以访问的内存类型。它类似于 Ascend C 中的 GlobalTensor,用于存储在整个 CUDA 程序中需要访问的数据。
CUDA 提供了 API 如 cudaMalloc 来分配全局内存,以及 cudaMemcpy 来在全局内存和主机内存之间复制数据。
Shared Memory in CUDA:
CUDA 中的共享内存(Shared Memory)与 Ascend C 的 LocalTensor 类似,是在 GPU 上更快的内存类型,它仅在单个 CUDA 块(block)内的所有线程之间共享。共享内存用于存储需要由块内多个线程协作处理的数据。
CUDA 内核函数中可以声明使用共享内存,并通过特定的 API 来加载和存储数据。
对于PPT中的代码片段:
SetGlobalBuffer 方法用于将数据设置或复制到 GlobalTensor 中,这在 CUDA 中可以通过直接访问全局内存指针或使用 cudaMemcpy 来实现。
GetValue 和 SetValue 方法用于访问和修改 LocalTensor 的元素,这可以通过在 CUDA 中声明共享内存数组并直接访问它们来实现。
单算子实现
host侧算子实现
kernel侧算子实现
Tilling
Local Memory的存储,无法完整的容纳算子的输入与输出,需要每次搬运一部分输入进行计算然后搬出,再搬运下一部分输入进行计算,直到得到完整的最终结果,这个数据切分、分块计算的过程称之为Tiling。根据算子的shape等信息来确定数据切分算法相关参数(比如每次搬运的块大小,以及总共循环多少次)的计算程序,称之为Tiling实现。
Shape推导
shape推导作用
1、算子参数的校验,实现程序健壮性并提高定位效率。
2、根据算子的输入张量描述、算子逻辑及算子属性,推理出算子的输出张量描述,包括张量的形状、数据类型及数据排布格式等信息。这样算子构图准备阶段就可以为所有的张量静态分配内存,避免动态内存分配带来的开销。
对比学习CUDA
Tiling in CUDA:
在 CUDA 中,Tiling 通常是指将数据分解成小块(tiles)以便于在 GPU 上进行有效计算的过程。由于 GPU 的共享内存相对于全局内存来说访问速度更快,所以这种方法可以减少全局内存访问次数和延迟,从而提高性能。
CUDA 开发者需要编写代码来确定每个 tile 的大小和如何在 kernel 内部进行迭代计算,以确保所有数据都被处理。这通常涉及到计算网格和块的大小,以及可能需要的循环次数。
Shape Inference in CUDA:
在 CUDA 中,形状推导不是内置功能,但是在深度学习框架中,如 TensorFlow 和 PyTorch,形状推导是框架层面提供的。对于自定义 CUDA 算子,开发者需要根据输入张量的维度和算子的操作来手动计算输出张量的形状。
形状推导有助于在运行内核之前确定内存分配的大小,并确保输入和输出张量的维度一致,这对于整体内存管理和程序的正确性至关重要。
算子功能调试
UT测试 (单元测试)
测试算子代码的正确性,验证输入输出结果与设计的一致性。UT侧重于保证算子程序能够正常运行,选取的场景组合应能覆盖算子代码的所有分支(一般情况下覆盖率要达到100%),从而降低不同场景下算子代码的编译失败率。
ST测试 (系统测试)
自定义算子部罢到算子库后,可进行ST (SystemTest)测试,在真实的硬件环境中,验证算子功能的正确性。
ST测试的主要功能是:基于算子测试用例定义文件。json生成单算子的om文件;使用AscendCL接口加载并执行单算子om文件,验证算子执行结果的正确性ST测试会覆盖算子实现文件,算子原型定义与算子信息库,不会对算子适配插件进行测试。
在 CUDA 中进行 UT 和 ST 测试时,还需要考虑 GPU 的异构计算特性,以及并行计算可能引入的同步和竞争条件问题。调试工具(如 CUDA GDB、Nsight Compute)和性能分析工具(如 NVIDIA Visual Profiler)在这些测试阶段也非常有用,可以帮助开发者定位问题和优化性能。具体来说,这两种只是测试过程的方法,与Ascend C没有什么本质上的区别,故此部分不做对比学习。
- 点赞
- 收藏
- 关注作者
评论(0)