【2023 · CANN训练营第一季】TIK C++算子开发入门第一章知识总结(下篇)

文章介绍了TIKC++中两种主要的数据结构——GlobalTensor和LocalTensor,分别用于存放外部存储和内部存储的数据,并展示了如何进行赋值和访问。接着,详细阐述了矢量计算指令的3级到0级接口API,从简单的运算符重载到复杂的自定义计算,以及0级接口的通用参数,如重复迭代次数、步长和Mask参数,用于实现灵活高效的计算。
摘要由CSDN通过智能技术生成

四、TIK C++常用数据结构

4.1、常用数据定义:GlobalTensor

        该参数主要是用来存放Global Memory(外部存储)的全局数据。

定义原型:

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

其中buffer--->主机侧传入的全局数据指针指向外部存储的起始地址。

其中buffersize--->设置GlobalTensor所包含的类型为T的数据个数,需自行保证不会超出实际数据的长度。

4.2、常用数据定义:LocalTensor

        该参数主要是用来存放核上Local Memory(内部存储)的数据。

定义原型:

template <typename T> class LocalTensor {
  T GetValue(const uint32_t offset) const;  // 获取 LocalTensor 中的某个值,返回 T 类型的立即数。
  template <typename T1> void SetValue(const uint32_t offset, const T1 value) const; // 设置 LocalTensor 中的某个值。offset单位为element
  // 获取距原LocalTensor起始地址偏移量为offset的新LocalTensor,注意offset不能超过原有LocalTensor的size大小。offset单位为element
  LocalTensor operator[](const uint32_t offset) const;  
  uint32_t GetSize() const; // 获取当前LocalTensor size大小
}
// input_local长度设置为256个int32_t类型的数据
// 示例1:对input_local中第i个位置进行赋值为100
for (int32_t i = 0; i < 256; ++i) {
  input_local.SetValue(i, 100);
}
// 结果:input_local为256个100 [100 100 100 ... 100]
// 示例2:获取input_local中第19个位置的数值
auto element = input_local.GetValue(19);
// 结果:element为100
// 示例3:获取input_local的长度,size大小为input_local有多少个element
auto size = input_local.GetSize();
// 结果:size大小为256
// 示例4:operator[]使用方法,output_local[50]为从起始地址开始偏移量为50的新tensor
Add(output_local[50], input_local[50], input_local2[50], 50);
// 结果如下:
// 输入数据(input_local):  [100 100 100 ... 100]  50个int32_t
// 输入数据(input_local2): [1 2 3 ... 50]         50个int32_t
// 输出数据(output_local): [101 102 103 ... 150]  50个int32_t

五、3/2/0级接口API的概念

5.1、矢量计算指令接口

矢量计算指令接口,能够启动AI Core中的Vector单元执行计算

为了降低开发者的使用门槛,指令按照由易到难,分成了3级到0级接口。其中3级接口最为简单,0级接口最为复杂,(1级接口还未发布)

多层级API封装的作用:

        1、降低复杂指令的使用难度

        2、跨代兼容性保障

        3、保留最大灵活度的可能

单目指令操作:Exp、Ln、Abs、Reciprocal、Sqrt、Rsqrt、Not、Relu、Sigmoid、Tanh、… 
双目指令操作:Add、Sub、Mul、Div、Max、Min、And、Or、…
标量双目指令操作:Adds、Muls、Maxs、Mins、ShiftLeft、ShiftRight、LeakyRelu、…
标量三目指令操作:Axpy、…
比较指令操作:Compare、…
选择指令操作:Select、ReduceV2、…
精度转换指令操作:Cast、…
规约指令操作:ReduceMax、ReduceMin、ReduceSum、…
特殊规约指令操作:WholeReduce、BlockReduce、PairReduce、…
数据转换操作:Transpose、TransDataTo5HD、…
数据填充操作:Duplicate、Brcb、…
数据搬移操作:DataCopy、Copy、…
……

 5.2、指令API的3级接口

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

以下指令API拥有3级接口:
Add:dstLocal = src0Local + src1Local;
Sub:dstLocal = src0Local - src1Local;
Mul:dstLocal = src0Local * src1Local;
Div:dstLocal = src0Local / src1Local;
And:dstLocal = src0Local & src1Local;
Or:dstLocal = src0Local | src1Local;
Compare:
dstLocal = src0Local < src1Local;
dstLocal = src0Local > src1Local;
dstLocal = src0Local <= src1Local;
dstLocal = src0Local >= src1Local;
dstLocal = src0Local == src1Local;
dstLocal = src0Local != src1Local;

注意:三级接口会进行连续矢量运算,运算量为目的LocalTensor的总长度。

5.3、指令API的2级接口

        2级连续计算接口,针对源操作数srcLocal的连续COUNT个数据进行计算,并连续写入目的操作数dstLocal,提供了一维Tensor的连续COUNT个数据的计算支持。

允许用户使用形如:

void Operator(const LocalTensor<T>& dstLocal, const LocalTensor<T>& srcLocal, const int32_t& calCount)

大多数指令API拥有2级接口,2级接口相对于3级接口,可以自定义运算量。

Exp(dstLocal, srcLocal, 512);
Adds(dstLocal, srcLocal, scalarValue, 512);
Select(dstLocal, maskLocal, src0Local, src1Local, SELMODE::VSEL_CMPMASK_SPR, 256);
ReduceMin(dstLocal, srcLocal, workLocal, 8320, true);
Duplicate(dstLocal, inputVal, 256);

注意:二级接口会进行连续矢量运算,开发者指定的运算量不能超过参与运算Tensor本身的大小。

5.4、指令API的0级接口

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

通用参数包括:

        Repeat times(迭代的次数)

        Block stride(单次迭代内不同block间地址步长)

        Repeat stride(相邻迭代间相同block的地址步长)

        Mask(用于控制参与运算的计算单元)

struct UnaryRepeatParams {
  uint32_t blockNumber = kDefaultBlkNum;
  uint16_t dstBlkStride = kDefaultBlkStride;
  uint16_t srcBlkStride = kDefaultBlkStride;
  uint8_t dstRepStride = kDefaultRepStride;
  uint8_t srcRepStride = kDefaultRepStride;
  bool repeatStrideMode = false;
  bool strideSizeMode = false;
  bool half_block = false;
};

Mask逐比特模式:

template <typename T> __aicore__ inline void Exp(const LocalTensor<T>& dstLocal, const LocalTensor<T>& srcLocal, uint64_t mask[2], const uint8_t repeatTimes, const UnaryRepeatParams& repeatParams);

Mask连续模式:

template <typename T> __aicore__ inline void Exp(const LocalTensor<T>& dstLocal, const LocalTensor<T>& srcLocal, uint64_t mask, const uint8_t repeatTimes, const UnaryRepeatParams& repeatParams);

5.5、指令API的0级接口的其他参数

重复迭代次数-Repeat times

        矢量计算单元,一次最多可以计算256Bytes的数据,每次读取连续的8个block(每个block 32Bytes,共256Bytes)数据进行计算,为完成对输入数据的处理,必须通过多次迭代(repeat)才能完成所有数据的读取与计算。 待处理数据大小为16个block(512Bytes),每次迭代处理8个block(256Bytes),需要两次迭代完成计算,Repeat times应设置为2。

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

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

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

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

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

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

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

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

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

        Block stride表示同意迭代内不同block的地址步长 如果需要控制单次迭代内,数据处理的步长,可以通过设置同一迭代内不同block的地址步长Block stride来实现。 连续计算,Block stride 设置为1,对同一迭代内的8个block数据连续进行处理 非连续计算,Block stride值大于1(如取2),同一迭代内不同block之间在读取数据时出现一个block的间隔。

Mask参数 

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

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

// int16_t数据类型单次迭代能处理的元素个数最大值为256/sizeof(int16_t) = 128,mask = 64,mask∈[1, 128],所以是合法输入
// repeatTimes = 1, 共128个元素,单次迭代能处理128个元素,故repeatTimes = 1
// dstBlkStride, src0BlkStride, src1BlkStride = 1, 单次迭代内连续读取和写入数据
// dstRepStride, src0RepStride, src1RepStride = 8, 迭代间的数据连续读取和写入
uint64_t mask = 64;
Add(dstLocal, src0Local, src1Local, mask, 1, { 1, 1, 1, 8, 8, 8 });
结果示例如下:
输入数据(src0Local): [1 2 3 ... 64 ...128]
输入数据(src1Local): [1 2 3 ... 64 ...128]
输出数据(dstLocal): [2 4 6 ... 128 undefined...undefined]
// int32_t数据类型单次迭代能处理的元素个数最大值为256/sizeof(int32_t) = 64,mask = 64,mask∈[1, 64],所以是合法输入
// repeatTimes = 1, 共64个元素,单次迭代能处理64个元素,故repeatTimes = 1
// dstBlkStride, src0BlkStride, src1BlkStride = 1, 单次迭代内连续读取和写入数据
// dstRepStride, src0RepStride, src1RepStride = 8, 迭代间的数据连续读取和写入
uint64_t mask = 64;
Add(dstLocal, src0Local, src1Local, mask, 1, { 1, 1, 1, 8, 8, 8 });
结果示例如下:
输入数据(src0Local): [1 2 3 ... 64]
输入数据(src1Local): [1 2 3 ... 64]
输出数据(dstLocal): [2 4 6 ... 128]

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

// 数据类型为int16_t
uint64_t mask[2] = {6148914691236517205, 6148914691236517205};
// repeatTimes = 1, 共128个元素,单次迭代能处理128个元素,故repeatTimes = 1。
// dstBlkStride, src0BlkStride, src1BlkStride = 1, 单次迭代内连续读取和写入数据。
// dstRepStride, src0RepStride, src1RepStride = 8, 迭代间的数据连续读取和写入。
Add(dstLocal, src0Local, src1Local, mask, 1, { 1, 1, 1, 8, 8, 8 });
结果示例如下:
输入数据(src0Local): [1 2 3 ... 64 ...127 128]
输入数据(src1Local): [1 2 3 ... 64 ...127 128]
输出数据(dstLocal): [2 undefined 6 ... undefined ... 254 undefined]
// 数据类型为int32_t
uint64_t mask[2] = {6148914691236517205, 0};
// repeatTimes = 1, 共64个元素,单次迭代能处理64个元素,故repeatTimes = 1。
// dstBlkStride, src0BlkStride, src1BlkStride = 1, 单次迭代内连续读取和写入数据。
// dstRepStride, src0RepStride, src1RepStride = 8, 迭代间的数据连续读取和写入。
Add(dstLocal, src0Local, src1Local, mask, 1, { 1, 1, 1, 8, 8, 8 });
结果示例如下:
输入数据(src0Local): [1 2 3 ... 63 64]
输入数据(src1Local): [1 2 3 ... 63 64]
输出数据(dstLocal): [2 undefined 6 ... undefined ... 126 undefined]

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值