3天学会Ascend C编程 | Day1 Ascend C基本概念及常用接口

本文分享自《【2023 · CANN训练营第一季】——Ascend C算子开发入门——第一次课》,作者:weixin_54022960 。

Ascend C是华为昇腾面向算子开发场景的编程语言,使用C/C++作为前端语言的算子开发工具,通过四层接口抽象、并行编程范式、孪生调试等技术,极大提高了算子的开发效率,助力AI开发者低成本完成算子开发和模型调优部署。

时间充足的小伙伴推荐去看官方教程:Ascend C官方教程

想省时省力快速入门可以看这篇文章,我会从以下几个方面帮助大家比较全面地了解AscendC编程最重要的知识点,做到3天快速上手。下面是第一节课学习要点:

  1. 核函数的定义及调用
  2. helloworld样例演示
  3. 常用数据定义
  4. 多层级API接口

使用Ascend C进行自定义算子开发的突出优势有:

1、C/C++原语编程

2、编程模型屏蔽硬件差异,编程范式提高开发效率

3、多层级API封装,从简单到灵活,兼顾易用与高效

4、孪生调试,CPU侧模拟NPU侧的行为,可现在CPU侧调试

当前Ascend C支持的AI处理器型号为昇腾310P AI处理器、昇腾910 AI处理器,其他型号暂不支持。

当前支持用户使用g++等C/C++编译器编译在cpu侧执行的Ascend C算子,并使用gdb单步调试;支持用户使用CCEC编译器编译在npu侧执行的Ascend C算子,实现加速计算。

核函数

核函数(Kernel Function)是Ascend C算子kernel侧实现的入口。Ascend C允许用户使用核函数这种C/C++函数的语法扩展来管理设备端的运行代码,用户在核函数中进行算子类对象的创建和其成员函数的调用,由此实现该算子的所有功能。核函数是主机端和设备端连接的桥梁。

核函数定义

核函数是直接在设备端执行的代码。在核函数中,需要为在一个核上执行的代码规定要进行的数据访问和计算操作,当核函数被调用时,多个核将并行执行同一个计算任务。核函数需要按照如下规则进行编写。


1、使用extern "C"

2、函数类型限定符


3、必须具有void返回类型

4、变量类型限定符

为了方便:指针入参变量统一的类型定义为__gm__ uint8_t*。用户统一使用uint8_t类型的指针,并在使用时转换为实际的指针类型;亦可直接传入实际的指针类型。

核函数调用

核函数的调用语句是C/C++函数调用语句的一种扩展。不同于常见的function_name(argument list)函数调用方式,核函数使用内核调用符<<<...>>>这种语法形式,来规定核函数的执行配置:


1、内核调用符这种调用方式,仅可在NPU侧编译时调用,CPU侧编译无法识别该符号。

2、核函数的调用是异步的,核函数的调用结束后,控制权立刻返回给主机端,可以调用aclrtSynchronizeStream函数来强制主机端程序等待所有核函数执行完毕。

3、算子执行的不同模式

Ascend C算子可用CPU模式或NPU模式执行

CPU模式:算子功能调试用,可以模拟在NPU上的计算行为,不需要依赖昇腾设备

NPU模式:算子功能/性能调试,可以使用NPU的强大算力进行运算加速


4、代码里使用内置宏 __CCE_KT_TEST__标识被宏包括的代码在CPU或NPU模式下编译。

#ifdef __CCE_KT_TEST__ 
//表示在CPU模式下会编译该段代码#ifndef __CCE_KT_TEST__ 
//表示在NPU模式下会编译该段代码

helloworld样例演示

1、代码

 2、编译与运行

常用数据定义

GlobalTensor

GlobalTensor用来存放Global Memory(外部存储)的全局数据。

template <typename T> class GlobalTensor {void SetGlobalBuffer(__gm__ T* buffer, uint32_t bufferSize); // 传入全局数据的指针,并手动设置一个buffer size,初始化GlobalTensor
}

buffer:主机侧传入的全局数据指针

bufferSize:所包含的类型为T的数据个数,单位为 element,需自行保证不会超出实际数据的长度

类型T支持所有数据类型,但需要遵循使用此GlobalTensor的指令的数据类型支持情况。

SetGlobalBuffer用于设置GlobalTensor的存储位置:buffer指向外部存储的起始地址,bufferSize为Tensor所占外部存储的大小,如指向的外部存储有连续256个int32_t,则其dataSize为256。
代码示例: 

void Init(__gm__ uint8_t *__restrict__ src_gm, __gm__ uint8_t *__restrict__ dst_gm)
{uint32_t dataSize = 256; //设置input_global的大小为256GlobalTensor<int32_t> inputGlobal; // 类型为int32_tinputGlobal.SetGlobalBuffer(reinterpret_cast<__gm__ int32_t *>(src_gm), dataSize); // 设置源操作数在Global Memmory上的起始地址为src_gm,所占外部存储的大小为256个int32_tLocalTensor<int32_t> inputLocal = inQueueX.AllocTensor<int32_t>();    DataCopy(inoutLocal, inputGlobal, dataSize); // 将Global Memmory上的inputGlobal拷贝到Local Memmory的inputLocal上...
}
 LocalTensor

用于存放AI Core中Local Memory(内部存储)的数据,支持QuePosition为VECIN、VECOUT、A1、A2、B1、B2、CO1、CO2。

template <typename T> class LocalTensor {T GetValue(const uint32_t offset) const;template <typename T1> void SetValue(const uint32_t offset, const T1 value) const;LocalTensor operator[](const uint32_t offset) const;uint32_t GetSize() const;
}

函数说明:类型T支持所有数据类型,但需要遵循使用此LocalTensor的指令的数据类型支持情况。

 代码示例:

// srcLen = 256, num = 100, M=50
// 示例1
for (int32_t i = 0; i < srcLen; ++i) {input_local.SetValue(i, num); // 对input_local中第i个位置进行赋值为num
}
// 示例1结果如下:
// 数据(input_local): [100 100 100  ... 100]// 示例2
for (int32_t i = 0; i < srcLen; ++i) {auto element = input_local.GetValue(i); // 获取input_local中第i个位置的数值
}
// 示例2结果如下:
// element 为100// 示例3
auto size = input_local.GetSize(); // 获取input_local的长度,size大小为input_local有多少个element
// 示例3结果如下:
// size大小为srcLen,256。// 示例4
Add(output_local[M], input_local[M], input_local2[M], M); // operator[]使用方法,output_local[M]为从起始地址开始偏移量为M的新tensor
// 示例4结果如下:
// 输入数据(input_local): [100 100 100 ... 100]
// 输入数据(input_local2): [1 2 3 ... 50]
// 输出数据(output_local): [101 102 103 ... 150]


多层级API接口

Ascend C提供了多层级的0-3级API,随着级别增高,API使用的自由度降低,易用性增强。开发者可以根据需要选择合适的API,使用最通俗易懂的高级接口快速搭建算子逻辑,使用自由灵活的低级接口进行复杂的逻辑实现和性能调优。这样做的主要作用是:

  • 降低复杂指令的使用难度
  • 跨代兼容性保障
  • 保留最大灵活度的可能

​3级接口

运算符重载,支持+, -, *, /, |, &, <, >, <=, >=, ==, !=,实现1级指令的简化表达。允许用户使用形如:dst = src0 * src1,针对整个Tensor进行计算,以下指令API拥有3级接口:


2级接口

针对源操作数srcLocal的连续COUNT个数据进行计算,并连续写入目的操作数dstLocal,解决一维tensor的连续计算问题。

0级接口

0级功能灵活计算接口,是最底层的开发接口,可以完整发回硬件优势的计算API,可以进行非连续计算,该功能可以充分发回CANN系列芯片的强大功能指令,支持对每个操作数的Block stride,Repeat stride,MASK的操作,允许用户使用诸多的通用参数来定制化所需要的操作:


1、重复迭代次数-Repeat times 

矢量计算单元,每次读取连续的8个block(每个block32 Bytes,共256 Bytes)数据进行计算,为完成对输入数据的处理,必须通过多次迭代(repeat)才能完成所有数据的读取与计算。Repeat times表示迭代的次数。

如下图所示,待处理数据大小为16个block(512Bytes),每次迭代处理8个block(256Bytes),需要两次迭代完成计算,Repeat times应设置为2。


2、相邻迭代间相同block的地址步长 

当Repeat times大于1,需要多次迭代完成矢量计算时,您可以根据不同的使用场景合理设置相邻迭代间相同block的地址步长Repeat stride的值。

连续计算场景:假设定义一个Tensor供目的操作数和源操作数同时使用(即地址重叠),Repeat stride取值为8。此时,矢量计算单元第一次迭代读取连续8个block,第二轮迭代读取下一个连续的8个block,通过多次迭代即可完成所有输入数据的计算。


非连续计算场景:Repeat stride取值大于8(如取10)时,则相邻迭代间矢量计算单元读取的数据在地址上不连续,出现2个block的间隔。

反复计算场景:Repeat stride取值为0时,矢量计算单元会对首个连续的8个block进行反复读取和计算。


部分重复计算:Repeat stride取值大于0且小于8时,相邻迭代间部分数据会被矢量计算单元重复读取和计算,此种情形一般场景不涉及。

 3、同一迭代内不同block的地址步长

如果需要控制单次迭代内,数据处理的步长,可以通过设置同一迭代内不同block的地址步长Block stride来实现。

  • 连续计算,Block stride 设置为1,对同一迭代内的8个block数据连续进行处理。
  • 非连续计算,Block stride值大于1(如取2),同一迭代内不同block之间在读取数据时出现一个block的间隔,如下图所示。


4、Mask参数

mask用于控制每次迭代内参与计算的元素。可通过连续模式和逐比特模式两种方式进行设置。

连续模式:表示前面连续的多少个元素参与计算。数据类型为uint64_t。取值范围和操作数的数据类型有关,数据类型不同,每次迭代内能够处理的元素个数最大值不同(当前数据类型单次迭代时能处理的元素个数最大值为:256 / sizeof(数据类型))。当操作数的数据类型占比特位16位时(如half,uint16_t),mask∈[1, 128];当操作数为32位时(如float, int32_t),mask∈[1, 64]。


逐bit模式:可以按位控制哪些元素参与计算,bit位的值为1表示参与计算,0表示不参与。参数类型为长度为2的uint64_t类型数组。参数取值范围和操作数的数据类型有关,数据类型不同,每次迭代内能够处理的元素个数最大值不同。当操作数为16位时,mask[0]、mask[1]∈[0, 264-1];当dst/src为32位时,mask[1]为0,mask[0]∈[0, 264-1]。

512个int16相加分别用0,2,3级接口实现对比,大家可以根据自己的实际需要选择对应的接口。

更多学习资源

好啦,本次分享结束啦,Ascend C的学习资源还有很多,想深入学习的可以参考官网教程:Ascend C官方教程

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

微服务系列文章 之 Nginx状态监控日志分析详解

1、Nginx状态监控 Nginx提供了一个内置的状态信息监控页面可用于监控Nginx的整体访问情况&#xff0c;这个功能由ngx_http_stub_status_module模块进行实现。 使用nginx -V 2>&1 | grep -o with-http_stub_status_module命令检测当前Nginx是否有status功能&#xff0c…

OpenMMLab MMTracking目标跟踪官方文档学习(一)

介绍 MMTracking 是PyTorch的开源视频感知工具箱。它是OpenMMLab项目的一部分。 它支持 4 个视频任务&#xff1a; 视频对象检测 (VID) 单目标跟踪 (SOT) 多目标跟踪 (MOT) 视频实例分割 (VIS) 主要特点 第一个统一视频感知平台 我们是第一个统一多功能视频感知任务的开源工…

数据结构day4(2023.7.18)

一、Xmind整理&#xff1a; 链表的插入和删除&#xff1a; 二、课上练习&#xff1a; 练习1&#xff1a;顺序表去重 33 22 22 11 11 i jfor(int i0;i<list->len-1;i){for(int ji1;j<len;j){if(list->data[i]list->data[j]){delete_by_sub(j,list); …

WebRTC基础

有用的网址&#xff1a; https://webrtc.org/ WebRTC API - Web API 接口参考 | MDN Browser APIs and Protocols: WebRTC - High Performance Browser Networking(OReilly) 浏览器中查看webrtc运行的实时信息&#xff1a; Chrome浏览器&#xff1a;chrome://webrtc-inter…

ArrayList和LinkedList

ArrayList的注意事项 1、在ArrayList中可以放任意元素&#xff0c;包括空值&#xff0c;任何元素&#xff0c;且可重复添加。 2、ArrayList底层是由数组来实现数据存储的 3、ArrayList基本等同于Vector&#xff0c;除了ArrayList是线程不安全(执行效率高),看源码 在多线程的…

依赖注入三种方式,以及传统xml文件传参,还包括@Bean方式传参,还有Resource注入(详细版,每步都有提及)

获取bean对象&#xff0c;也称为对象装配&#xff0c;对象注入&#xff0c;依赖注入。 对象装配的实现方法有3种&#xff1a; 1.属性注入&#xff1b; 2.构造方法注入&#xff1b; 3.Setter注入。 再讲本节内容之前&#xff0c;我们先来提两个传参的方式&#xff0c;首先呢…

新手如何自学PostgreSQL(PG)

如果你是一个新手&#xff0c;想要自学PostgreSQL&#xff0c;下面是一些步骤和资源&#xff0c;可以帮助你入门&#xff1a; ①了解数据库基础知识&#xff1a;在开始学习PostgreSQL之前&#xff0c;建议你先了解一些数据库的基础概念和术语&#xff0c;例如表、列、行、SQL查…

什么是芯片组,南桥与北桥芯片的作用与区别

主板是连接计算机所有部件的PCB。在老式计算机中&#xff0c;所有芯片都分布在主板上。在现代计算机中&#xff0c;芯片数量减少并集中在特定位置。因此&#xff0c;将多个芯片组合起来形成一个芯片。这种可以替代大量芯片的芯片称为芯片组。主板上有一个芯片组。芯片组处理CPU…

echarts开发遇到的问题

echarts开发遇到的问题 1.rich富文本标签作为横向柱状图的刻度标签&#xff0c;其中带有icon。rich里不能写参数&#xff0c;只能写死&#xff1f;圆角设置无效&#xff1f; 解决办法&#xff1a; 自己写横向柱状图 散点图性能优化配置的临界点&#xff0c;最低优化数值必须…

day52

思维导图 比较指令结果的条件码 练习 汇编实现1-100的累加 .text .global _strat _start: mov r0,#0mov r1,#0 add_fun:add r0,r0,#1cmp r0,#100addls r1,r1,r0bls add_fun .end

微信小程序基于Promise封装发起网络请求

1.创建一个request.js // 相当于域名 const baseURL ***************; // 暴露一个request函数 export function request(parms) {// 路径拼接const url baseURL parms.url;// 请求体&#xff0c;默认为{}const data parms.data || {};// 请求方式&#xff0c;默认为GETco…

线性DP———最长公共子序列问题(LCS)

LCS问题 求两序列具有相同元素的最长子序列&#xff0c;我们可以用到动态规划的方法来解决问题 我们用 来表示序列 与序列 能组成的LCS的长度&#xff0c;的状态转移方程如下&#xff1a; 使用两层for循环就可以解决此问题&#xff0c;时间复杂度为,可以处理n<7000左右…