寒武纪显卡实现softmax算子

寒武纪显卡实现softmax基本逻辑

寒武纪实现softmax包括下面5个步骤,我们也采取5个kernel来实现softmax:
unionMaxKernel(float* middle, float* source1, int num),这个kernel使用的任务类型是union1,其中middle的长度为taskDim,达到的目的是middle对应元素存储的是不同taskId处理的那部分数据的局部max

blockMaxKernel(float* dstMax, float* middle, int taskNum),这个kernel使用的任务类型是BLOCK,任务总数也是1,其中dstMax的长度为1,达到的目的是将长度为taskDim的middle进一步做max规约得到全局max

unionSumKernel(float* middle, float* source1, int num, float globalMax),这个kernel使用的任务类型是union1,其中middle的长度为taskDim,达到的目的是middle对应元素存储的是不同taskId处理的那部分数据的局部数值和,其中globalMax就是上面两个kernel计算出来的全局max
这个函数特别值得注意的是,由于寒武纪显卡无法直接针对向量source1的特定元素进行操作,为此我们引入了一个中间向量tmp[maxNum],其中tmp每个元素都是-globalMax,借助于__bangd_add来实现source1 = source1 + tmp。除此之外,寒武纪有3个实现exp的函数,但是经过查证,这三个exp函数的数值精度差距很大,由于经过线性变换以后source1的元素都在负半轴,因此这里我们使用__bang_active_exp_less_0函数来提高精度,详细内容可以参考官网链接说明

blockSumKernel(float *dstSum, float *middle, int taskNum),这个kernel使用的任务类型是BLOCK,任务总数也是1,其中dstSum的长度为1,达到的目的是将长度为taskDim的middle进一步做sum规约得到全局sum

softmaxKernel(float *dst, float *source1, float globalMax, float globalSum, int num),这个kernel使用的dst,source1长度都是num,globalMax,globalSum在上面已经计算出来,最终希望dst存储最终的softmax结果。
编译命令为:

cncc softmax.mlu -o softmax --bang-mlu-arch=mtp_372 -O3

#include <bang.h>
#include <bang_device_functions.h>
#define EPS 1e-7
const int NRAM_MAX_SIZE = 1024 * 128;//后续树状求和必须保证NRAM_MAX_SIZE为2的幂次
const int maxNum = NRAM_MAX_SIZE/sizeof(float); //NRAM上最多存储maxNum个float元素
const int warpSize = 32;//__bang_reduce_sum每次从src取128字节数据相加,对应32个float元素,并且0-31的结果保存在索引0,32-63的结果保存在索引1

__nram__ float src1[maxNum];//每次搬运maxNum数据到NRAM
__nram__ float destMax;//方便后面比较最大值
__nram__ float destSum[maxNum];//后面数值求和
__nram__ float destSumFinal[warpSize];//将destSum规约到destFinal[0]
__mlu_entry__ void unionMaxKernel(float* middle, float* source1, int num) {
  
  int remain = num%taskDim;//如果不能整除,则让前部分taskId多处理一个元素
  int stepEasy = (num - remain)/taskDim;
  int stepHard = stepEasy + 1;
  int step = (taskId < remain ? stepHard : stepEasy);//前部分tsakId多处理一个元素
  int indStart = (taskId < remain ? taskId * stepHard : remain * stepHard + (taskId - remain) * stepEasy);
  int remainNram = step%maxNum;
  int repeat = step/maxNum;//如果一个task处理元素个数超出NRAM最大内存,则需要for循环
  //maxNum尽量取大一些,免得repeat过大导致求和过程累加过于严重,使得大数吃小数
  source1 = source1 + indStart;//设定起始偏移量

  
  destMax = -INFINITY;//初始化为负无穷
  
  for(int i = 0; i < repeat; i++){
    __memcpy(src1, source1 + i * maxNum, maxNum * sizeof(float), GDRAM2NRAM);
    __bang_argmax(src1, src1, maxNum);//针对taskId处理的这step数据,借助于for循环把信息集中到长度为maxNum的向量src1中
    if(destMax < src1[0]){
      destMax = src1[0];
    }
  }
 
  if(remainNram){
    __bang_write_value(src1, maxNum, -INFINITY);//必须要初始化src1全部元素为负无穷
    __memcpy(src1, source1 + repeat * maxNum, remainNram * sizeof(float), GDRAM2NRAM);
    __bang_argmax(src1, src1, maxNum);//针对taskId处理的这step数据,借助于for循环把信息集中到长度为maxNum的向量src1中
    if(destMax < src1[0]){
      destMax = src1[0];
    }
  }//结束以后向量destMax保存了source1[indSart:indStart+step]这部分数据的全局最大值
  
  __memcpy(middle + taskId, &destMax, sizeof(float), NRAM2GDRAM);//middle长度为taskDim
}
//----------------------

__mlu_entry__ void blockMaxKernel(float* dstMax, float* middle, int taskNum) {//将长度为taskDim的middle继续做Max规约
  
  int remain = taskNum%warpSize;
  int repeat = (taskNum - remain)/warpSize;//如果taskDim太大,超过warpSize,使用for循环规约
  __nram__ float srcMid[warpSize];
  
  destMax = -INFINITY;
  for(int i = 0; i < repeat; i++){
    __memcpy(srcMid, middle + i * warpSize, warpSize * sizeof(float), GDRAM2NRAM);//每次迁移32个float数据到NRAM
    __bang_argmax(srcMid, srcMid, warpSize);
    if(destMax < srcMid[0]){
      destMax = srcMid[0];
    }
  }
  if(remain){
    __bang_write_value(srcMid, warpSize, -INFINITY);//初始化srcMid全部元素为负无穷
    __memcpy(srcMid, middle + repeat * warpSize, remain * sizeof(float), GDRAM2NRAM);
    __bang_argmax(srcMid, srcMid, warpSize);
    if(destMax < srcMid[0]){
      destMax = srcMid[0];
    }
  }
 
  __memcpy(dstMax, &destMax, sizeof(float), NRAM2GDRAM);//这个kernel只能使用Block类型,1个任务
}

__mlu_entry__ void unionSumKernel(float* middle, float* source1, int num, float globalMax) {
  
  int remain = num%taskDim;//如果不能整除,则让前部分taskId多处理一个元素
  int stepEasy = (num - remain)/taskDim;
  int stepHard = stepEasy + 1;
  int step = (taskId < remain ? stepHard : stepEasy);//前部分tsakId多处理一个元素
  int indStart = (taskId < remain ? taskId * stepHard : remain * stepHard + (taskId - remain) * stepEasy);
  int remainNram = step%maxNum;
  int repeat = step/maxNum;//如果一个task处理元素个数超出NRAM最大内存,则需要for循环
  //maxNum尽量取大一些,免得repeat过大导致求和过程累加过于严重,使得大数吃小数
  source1 = source1 + indStart;//设定起始偏移量

  __nram__ float tmp[maxNum];

  __bang_write_value(tmp, maxNum, -globalMax);//初始化tmp全部元素为-globalMax
  __bang_write_zero(destSum, maxNum);
  for(int i = 0; i < repeat; i++){
    __memcpy(src1, source1 + i * maxNum, maxNum * sizeof(float), GDRAM2NRAM);
    __bang_add(src1, tmp, src1, maxNum);//src1 = src1 - globalMax 
    __bang_active_exp_less_0(src1, src1, maxNum);//src1 = exp(src1 - globalMax)
    //__bang_active_exphp(src1, src1, maxNum);//src1 = exp(src1 - globalMax)
    __bang_add(destSum, destSum, src1, maxNum);//destSum += exp(src1 - globalMax)
  }
  if(remainNram){
    __bang_write_value(src1, maxNum, globalMax);
    __memcpy(src1, source1 + repeat * maxNum, remainNram * sizeof(float), GDRAM2NRAM);
    __bang_add(src1, tmp, src1, maxNum);//src1 = src1 - globalMax ,后面maxNum-remainNram这部分直接为0
    
    __bang_active_exp_less_0(src1, src1, maxNum);//src1 = exp(src1 - globalMax)
    //__bang_active_exphp(src1, src1, maxNum);//src1 = exp(src1 - globalMax)
    
    __bang_add(destSum, destSum, src1, maxNum);//destSum在原来基础上又多加了(maxNum - remainNram)
    
  }//结束以后长度为maxNum的向量destSum保存了source1[indSart:indStart+step]这部分数据的数值和+(maxNum - remainNram)
  //__bang_printf("destSum[%d]:%.6f, src1:%.6f\n",remainNram, destSum[remainNram], src1[remainNram]);
  //下面开始针对destSum做规约
  __bang_write_zero(destSumFinal, warpSize);//初始化destSumFinal全部元素为0
  int segNum = maxNum / warpSize;//将destSum分成segNum段,每段向量长度为warpSize,分段进行树状求和,segNum要求是2的幂次
  for(int strip = segNum/2; strip > 0; strip = strip / 2){//segNum要求是2的幂次即maxNum必须选取2的幂次
    for(int i = 0; i < strip ; i++){
      __bang_add(destSum + i * warpSize, destSum + i * warpSize, destSum + (i + strip) * warpSize, warpSize);
    } 
  }
  __bang_reduce_sum(destSumFinal, destSum, warpSize);
  destSumFinal[0] = destSumFinal[0] - (maxNum - remainNram);//把上面多加的(maxNum - remainNram)减掉
  //__bang_printf("taskId:%d,maxNum - remainNram:%d,but get sum:%.6f\n",taskId, maxNum - remainNram, destSumFinal[0]);
  __memcpy(middle + taskId, destSumFinal, sizeof(float), NRAM2GDRAM);
}
//----------------------

__mlu_entry__ void blockSumKernel(float *dstSum, float *middle, int taskNum)//将长度为taskDim的middle继续做Sum规约
{
    
    int remain = taskNum % warpSize;
    int repeat = (taskNum - remain) / warpSize;
    __nram__ float srcMid[warpSize];
    __bang_write_zero(destSumFinal, warpSize); // 初始化destSumFinal全部元素为0
    //__bang_printf("sum:%.6f\n",destSumFinal[0]);
    for (int i = 0; i < repeat; i++)
    {
        __memcpy(srcMid, middle + i * warpSize, warpSize * sizeof(float), GDRAM2NRAM); // 每次迁移32个float数据到NRAM
        __bang_add(destSumFinal, destSumFinal, srcMid, warpSize);                      // destSumFinal存储add结果
    }
    if (remain)
    {
        __bang_write_zero(srcMid, warpSize); // 初始化destSumFinal全部元素为0
        __memcpy(srcMid, middle + repeat * warpSize, remain * sizeof(float), GDRAM2NRAM);
        __bang_add(destSumFinal, destSumFinal, srcMid, warpSize); // destSumFinal存储add结果
    }
    __bang_reduce_sum(destSumFinal, destSumFinal, warpSize); // 针对destSumFinal规约即可把结果保存到destSumFinal[0]
    //__bang_printf("xiao,taskId:%d,sum:%.6f\n", taskId, destSumFinal[0]);
    __memcpy(dstSum, destSumFinal, sizeof(float), NRAM2GDRAM); // 这个kernel只能使用Block类型,1个任务
}
__mlu_entry__ void softmaxKernel(float *dst, float *source1, float globalMax, float globalSum, int num){
  int remain = num%taskDim;//如果不能整除,则让前部分taskId多处理一个元素
  int stepEasy = (num - remain)/taskDim;
  int stepHard = stepEasy + 1;
  int step = (taskId < remain ? stepHard : stepEasy);//前部分tsakId多处理一个元素
  int indStart = (taskId < remain ? taskId * stepHard : remain * stepHard + (taskId - remain) * stepEasy);
  int remainNram = step%maxNum;
  int repeat = step/maxNum;//如果一个task处理元素个数超出NRAM最大内存,则需要for循环
  //maxNum尽量取大一些,免得repeat过大导致求和过程累加过于严重,使得大数吃小数
  source1 = source1 + indStart;//设定起始偏移量
  dst = dst + indStart;//设定起始偏移量
  float globalSumInv = 1.0/globalSum;
  
  for(int i = 0; i < repeat; i++){
    __bang_write_value(destSum, maxNum, -globalMax);//初始化destSum全部元素为-globalMax
    __memcpy(src1, source1 + i * maxNum, maxNum * sizeof(float), GDRAM2NRAM);
    __bang_add(src1, destSum, src1, maxNum);//src1 = src1 - globalMax 
    __bang_active_exp_less_0(src1, src1, maxNum);//src1 = exp(src1 - globalMax)
    //__bang_active_exphp(src1, src1, maxNum);//src1 = exp(src1 - globalMax)
    __bang_write_value(destSum, maxNum, globalSumInv);//初始化destSum全部元素为globalSumInv,使用1.0/globalSum编译报错
    __bang_mul(src1, src1, destSum, maxNum);//倒数和另一个向量逐元素相乘得到除法结果
    __memcpy(dst + i * maxNum, src1, maxNum * sizeof(float), NRAM2GDRAM);
  }
  
  if(remainNram){ 
    __bang_write_value(src1, maxNum, -globalMax);
    __bang_write_value(destSum, maxNum, -globalMax);//初始化destSum全部元素为-globalMax
    __memcpy(src1, source1 + repeat * maxNum, remainNram * sizeof(float), GDRAM2NRAM);
    __bang_add(src1, destSum, src1, maxNum);//src1 = src1 - globalMax 
    __bang_active_exp_less_0(src1, src1, maxNum);//src1 = exp(src1 - globalMax)
    //__bang_active_exphp(src1, src1, maxNum);//src1 = exp(src1 - globalMax)
    __bang_write_value(destSum, maxNum, globalSumInv);//初始化destSum全部元素为globalSumInv,使用1.0/globalSum编译报错
    __bang_mul(src1, src1, destSum, maxNum);//倒数和另一个向量逐元素相乘得到除法结果
    __memcpy(dst + repeat * maxNum, src1, remainNram * sizeof(float), NRAM2GDRAM);
  }
  //__bang_printf("Inv:%.6f\n",globalSumInv);
}
int main(void)
{
  int num = 102001010;
  //int num = 10;
  cnrtQueue_t queue;
  CNRT_CHECK(cnrtSetDevice(0));
  CNRT_CHECK(cnrtQueueCreate(&queue));

  cnrtDim3_t dim = {4, 1, 1};
  int taskNum = dim.x * dim.y * dim.z;
  cnrtFunctionType_t ktype = CNRT_FUNC_TYPE_UNION1;

  cnrtNotifier_t start, end;
  CNRT_CHECK(cnrtNotifierCreate(&start));
  CNRT_CHECK(cnrtNotifierCreate(&end));

  float* host_dst = (float*)malloc(num * sizeof(float));
  float* host_src1 = (float*)malloc(num * sizeof(float));
  

  for (int i = 0; i < num; i++) {
    host_src1[i] = i%4;
  }

  float* mlu_middle;
  float* mlu_dstMax;
  float* mlu_dstSum;
  float* mlu_dst;
  float* mlu_src1;
  
  CNRT_CHECK(cnrtMalloc((void**)&mlu_middle, taskNum * sizeof(float)));
  CNRT_CHECK(cnrtMalloc((void**)&mlu_dstMax, sizeof(float)));
  CNRT_CHECK(cnrtMalloc((void**)&mlu_dstSum, sizeof(float)));
  CNRT_CHECK(cnrtMalloc((void**)&mlu_dst, num * sizeof(float)));
  CNRT_CHECK(cnrtMalloc((void**)&mlu_src1, num * sizeof(float)));
 

  CNRT_CHECK(cnrtMemcpy(mlu_src1, host_src1, num * sizeof(float), cnrtMemcpyHostToDev));
  
  //----------------------------
  CNRT_CHECK(cnrtPlaceNotifier(start, queue));
  unionMaxKernel<<<dim, ktype, queue>>>(mlu_middle, mlu_src1, num);
  
  cnrtQueueSync(queue);
  //---------------------------
  cnrtDim3_t dimBlock = {1, 1, 1};
  
  blockMaxKernel<<<dimBlock, CNRT_FUNC_TYPE_BLOCK, queue>>>(mlu_dstMax, mlu_middle, taskNum);
  
  cnrtQueueSync(queue);
  float globalMax;
  CNRT_CHECK(cnrtMemcpy(&globalMax, mlu_dstMax, sizeof(float), cnrtMemcpyDevToHost));
  printf("max:%.6f\n",globalMax);
  //----------------------------
  
  unionSumKernel<<<dim, ktype, queue>>>(mlu_middle, mlu_src1, num, globalMax);
  
  cnrtQueueSync(queue);
  //---------------------------
  
  blockSumKernel<<<dimBlock, CNRT_FUNC_TYPE_BLOCK, queue>>>(mlu_dstSum, mlu_middle, taskNum); 
  cnrtQueueSync(queue);
  float globalSum;
  CNRT_CHECK(cnrtMemcpy(&globalSum, mlu_dstSum, sizeof(float), cnrtMemcpyDevToHost));
  printf("sum:%.6f\n",globalSum);
  //----------------------------
  softmaxKernel<<<dim, ktype, queue>>>(mlu_dst, mlu_src1, globalMax, globalSum, num);
  CNRT_CHECK(cnrtPlaceNotifier(end, queue));
  cnrtQueueSync(queue);
  //printf("max:%.6f,sum:%.6f\n", globalMax ,globalSum);
  //---------------------------
  CNRT_CHECK(cnrtMemcpy(host_dst, mlu_dst, num * sizeof(float), cnrtMemcpyDevToHost));
  for(int i = 0; i < 10; i++){
    printf("softmax[%d]:%.6e,origin:%.6f\n", i, host_dst[i], host_src1[i]);
  }
  float timeTotal;
  CNRT_CHECK(cnrtNotifierDuration(start, end, &timeTotal));
  printf("Total Time: %.3f ms\n", timeTotal / 1000.0);

  CNRT_CHECK(cnrtQueueDestroy(queue));

  cnrtFree(mlu_middle);
  cnrtFree(mlu_src1);
  
  free(host_dst);
  free(host_src1);
  

  return 0;
}




softmax融合

在这里插入图片描述
借助于__bang_atomic_add和__bang_atomic_max两个函数,我们可以把上面的5个kernel融合成一个kernel,代码框架逻辑和上面完全一样,我们以__bang_atomic_add函数来重点介绍一下寒武纪显卡的原子操作,该函数涉及到三个向量的原子操作,其中dst和src2都是NRAM上的向量,但是src1是__mlu_device__上的向量,对于我们的代码,比如说上面的sum规约例子,不同的taskId最后都会得到一个结果保存在destSumFinal[0],这个结果保存的是taskId对应的这部分数据的数值和,而我们需要获得全局数值和。之前的做法是,我们把不同taskId的结果保存在全局内存变量middle里面,middle的长度为taskDim,然后借助于另一个kernel对middle规约。现在我们有了原子操作,我们可以借助于原子操作把不同taskId的结果直接累加到全局变量里面。
在这里插入图片描述
上面三行代码结束以后,不管是哪个taskId,计算出来的globalSum都是一样的结果,也就是不同taskId对应的destSumFinal[0]相加的结果,这里特别值得注意的就是调用原子操作之前一定要先做同步,保证不同taskId都计算结束,其次就是globalSum必须设置为指针型向量,不能使用常数。详细代码如下所示:

#include <bang.h>
#include <bang_device_functions.h>
#define EPS 1e-7
const int NRAM_MAX_SIZE = 1024 * 128;//后续树状求和必须保证NRAM_MAX_SIZE为2的幂次
const int maxNum = NRAM_MAX_SIZE/sizeof(float); //NRAM上最多存储maxNum个float元素
const int warpSize = 32;//__bang_reduce_sum每次从src取128字节数据相加,对应32个float元素,并且0-31的结果保存在索引0,32-63的结果保存在索引1

__nram__ float src1[maxNum];//每次搬运maxNum数据到NRAM
__nram__ float destMax;//方便后面比较最大值
__nram__ float destSum[maxNum];//后面数值求和
__nram__ float destSumFinal[warpSize];//将destSum规约到destFinal[0]

__mlu_entry__ void softmaxKernel(float* dst, float* source1, float* globalMax, float* globalSum, int num) {
  
  int remain = num%taskDim;//如果不能整除,则让前部分taskId多处理一个元素
  int stepEasy = (num - remain)/taskDim;
  int stepHard = stepEasy + 1;
  int step = (taskId < remain ? stepHard : stepEasy);//前部分tsakId多处理一个元素
  int indStart = (taskId < remain ? taskId * stepHard : remain * stepHard + (taskId - remain) * stepEasy);
  int remainNram = step%maxNum;
  int repeat = step/maxNum;//如果一个task处理元素个数超出NRAM最大内存,则需要for循环
  //maxNum尽量取大一些,免得repeat过大导致求和过程累加过于严重,使得大数吃小数
  source1 = source1 + indStart;//设定起始偏移量

  //------------------------------------下面开始计算max
  destMax = -INFINITY;//初始化为负无穷
  for(int i = 0; i < repeat; i++){
    __memcpy(src1, source1 + i * maxNum, maxNum * sizeof(float), GDRAM2NRAM);
    __bang_argmax(src1, src1, maxNum);//针对taskId处理的这step数据,借助于for循环把信息集中到长度为maxNum的向量src1中
    if(destMax < src1[0]){
      destMax = src1[0];
    }
  }
  if(remainNram){
    __bang_write_value(src1, maxNum, -INFINITY);//必须要初始化src1全部元素为负无穷
    __memcpy(src1, source1 + repeat * maxNum, remainNram * sizeof(float), GDRAM2NRAM);
    __bang_argmax(src1, src1, maxNum);//针对taskId处理的这step数据,借助于for循环把信息集中到长度为maxNum的向量src1中
    if(destMax < src1[0]){
      destMax = src1[0];
    }
  }//结束以后向量destMax保存了source1[indSart:indStart+step]这部分数据的全局最大值
  globalMax[0] = -INFINITY;
  __sync_all();
  
  __bang_atomic_max(&destMax, globalMax, &destMax, 1);//globalMax[0]必须初始化为负无穷
  
  //------------------------------------下面开始计算sum
  __nram__ float tmp[maxNum];
  __bang_write_value(tmp, maxNum, -globalMax[0]);//初始化tmp全部元素为-globalMax[0]
  __bang_write_zero(destSum, maxNum);
  for(int i = 0; i < repeat; i++){
    __memcpy(src1, source1 + i * maxNum, maxNum * sizeof(float), GDRAM2NRAM);
    __bang_add(src1, tmp, src1, maxNum);//src1 = src1 - globalMax[0] 
    __bang_active_exp_less_0(src1, src1, maxNum);//src1 = exp(src1 - globalMax[0])
    __bang_add(destSum, destSum, src1, maxNum);//destSum += exp(src1 - globalMax[0])
  }
  if(remainNram){
    __bang_write_value(src1, maxNum, globalMax[0]);
    __memcpy(src1, source1 + repeat * maxNum, remainNram * sizeof(float), GDRAM2NRAM);
    __bang_add(src1, tmp, src1, maxNum);//src1 = src1 - globalMax[0] ,后面maxNum-remainNram这部分直接为0
    __bang_active_exp_less_0(src1, src1, maxNum);//src1 = exp(src1 - globalMax[0])
    __bang_add(destSum, destSum, src1, maxNum);//destSum在原来基础上又多加了(maxNum - remainNram)
    
  }//结束以后长度为maxNum的向量destSum保存了source1[indSart:indStart+step]这部分数据的数值和+(maxNum - remainNram)
  
  __bang_write_zero(destSumFinal, warpSize);//初始化destSumFinal全部元素为0
  int segNum = maxNum / warpSize;//将destSum分成segNum段,每段向量长度为warpSize,分段进行树状求和,segNum要求是2的幂次
  for(int strip = segNum/2; strip > 0; strip = strip / 2){//segNum要求是2的幂次即maxNum必须选取2的幂次
    for(int i = 0; i < strip ; i++){
      __bang_add(destSum + i * warpSize, destSum + i * warpSize, destSum + (i + strip) * warpSize, warpSize);
    } 
  }
  __bang_reduce_sum(destSumFinal, destSum, warpSize);
  destSumFinal[0] = destSumFinal[0] - (maxNum - remainNram);//把上面多加的(maxNum - remainNram)减掉
  __sync_all();
  globalSum[0] = 0.0;
  __bang_atomic_add(destSumFinal, globalSum, destSumFinal, 1);//globalSum[0]必须初始化为0
  //------------------------------------下面开始计算softmax
  dst = dst + indStart;//设定起始偏移量
  float globalSumInv = 1.0/globalSum[0];
  
  for(int i = 0; i < repeat; i++){
    __bang_write_value(destSum, maxNum, -globalMax[0]);//初始化destSum全部元素为-globalMax[0]
    __memcpy(src1, source1 + i * maxNum, maxNum * sizeof(float), GDRAM2NRAM);
    __bang_add(src1, destSum, src1, maxNum);//src1 = src1 - globalMax[0] 
    __bang_active_exp_less_0(src1, src1, maxNum);//src1 = exp(src1 - globalMax[0])
    __bang_write_value(destSum, maxNum, globalSumInv);//初始化destSum全部元素为globalSum[0]Inv,使用1.0/globalSum[0]编译报错
    __bang_mul(src1, src1, destSum, maxNum);//倒数和另一个向量逐元素相乘得到除法结果
    __memcpy(dst + i * maxNum, src1, maxNum * sizeof(float), NRAM2GDRAM);
  }
  
  if(remainNram){ 
    __bang_write_value(src1, maxNum, -globalMax[0]);
    __bang_write_value(destSum, maxNum, -globalMax[0]);//初始化destSum全部元素为-globalMax[0]
    __memcpy(src1, source1 + repeat * maxNum, remainNram * sizeof(float), GDRAM2NRAM);
    __bang_add(src1, destSum, src1, maxNum);//src1 = src1 - globalMax[0] 
    __bang_active_exp_less_0(src1, src1, maxNum);//src1 = exp(src1 - globalMax[0])
    __bang_write_value(destSum, maxNum, globalSumInv);//初始化destSum全部元素为globalSum[0]Inv,使用1.0/globalSum[0]编译报错
    __bang_mul(src1, src1, destSum, maxNum);//倒数和另一个向量逐元素相乘得到除法结果
    __memcpy(dst + repeat * maxNum, src1, remainNram * sizeof(float), NRAM2GDRAM);
  }
  
  __bang_printf("taskId:%d,max:%.6f, sum:%.6f\n",taskId, globalMax[0], globalSum[0]);
}


int main(void)
{
  int num = 102001010;
  //int num = 10;
  cnrtQueue_t queue;
  CNRT_CHECK(cnrtSetDevice(0));
  CNRT_CHECK(cnrtQueueCreate(&queue));

  cnrtDim3_t dim = {4, 1, 1};
  int taskNum = dim.x * dim.y * dim.z;
  cnrtFunctionType_t ktype = CNRT_FUNC_TYPE_UNION1;

  cnrtNotifier_t start, end;
  CNRT_CHECK(cnrtNotifierCreate(&start));
  CNRT_CHECK(cnrtNotifierCreate(&end));

  float* host_dst = (float*)malloc(num * sizeof(float));
  float* host_src1 = (float*)malloc(num * sizeof(float));
  

  for (int i = 0; i < num; i++) {
    host_src1[i] = i%4;
  }

  float* mlu_dst;
  float* mlu_src1;
  float* globalMax;
  float* globalSum;
  CNRT_CHECK(cnrtMalloc((void**)&mlu_dst, num * sizeof(float)));
  CNRT_CHECK(cnrtMalloc((void**)&mlu_src1, num * sizeof(float)));
  CNRT_CHECK(cnrtMalloc((void**)&globalMax, sizeof(float)));
  CNRT_CHECK(cnrtMalloc((void**)&globalSum, sizeof(float)));

  CNRT_CHECK(cnrtMemcpy(mlu_src1, host_src1, num * sizeof(float), cnrtMemcpyHostToDev));
  
  //----------------------------
  CNRT_CHECK(cnrtPlaceNotifier(start, queue));
  softmaxKernel<<<dim, ktype, queue>>>(mlu_dst, mlu_src1, globalMax, globalSum, num);
  CNRT_CHECK(cnrtPlaceNotifier(end, queue));
  cnrtQueueSync(queue);

  //---------------------------
  CNRT_CHECK(cnrtMemcpy(host_dst, mlu_dst, num * sizeof(float), cnrtMemcpyDevToHost));
  for(int i = 0; i < 10; i++){
    printf("softmax[%d]:%.6e,origin:%.6f\n", i, host_dst[i], host_src1[i]);
  }
  float timeTotal;
  CNRT_CHECK(cnrtNotifierDuration(start, end, &timeTotal));
  printf("Total Time: %.3f ms\n", timeTotal / 1000.0);

  CNRT_CHECK(cnrtQueueDestroy(queue));

  cnrtFree(mlu_dst);
  cnrtFree(mlu_src1);
  cnrtFree(globalMax);
  cnrtFree(globalSum);
  
  free(host_dst);
  free(host_src1);
  

  return 0;
}




softmax同时计算max和sum

回忆之前英伟达平台编写softmax的经验,当时我们引入了一个结构体来存储计算过程中的max和数值和,
在这里插入图片描述
这里我们也采取类似的思路来减少从GDRM到NRAM的数据访问频率,我们以下面这段代码为例子重点介绍如何运用寒武纪框架一边计算全局max,一边计算数值和。
代码29行初始化一个变量destNewMax为负无穷,方便后面比较最大值
进入循环以后,代码31行开始根据循环体不断读取全局变量不同位置的数据,将数值转移到NRAM数组src1上
代码32行借助于函数__bang_argmax计算出src1的最大值和对应的索引,注意__bang_argmax的两个向量参数长度必须相同,但是第一个向量计算以后索引为0的位置存储的是value,索引为1的位置存储的是index
代码33行——35行开始判断是否要更新最大值destNewMax
代码36行——38行其实就是在做一个src1 = exp(src1-destNewMax),只不过本人一时半会没有找到一个函数可以直接让向量减去一个常数,为此这里使用__bang_add来达成目的
代码39行——44行,这里必须要判断当前循环数是否大于0,i = 0的时候destSum = src1即可,但是一旦i大于0,destSum = destSum×exp(destOldMax - destNewMax) + src1,这一步就是保证数值和正确的关键,每次循环都会更新最大值,所以每次循环都要对上一次的数值和destSum做一个缩放,并且每次循环都需要更新destOldMax的数值
代码46行——61行这部分代码的逻辑和上面这个循环类似,只不过是针对无法整除多余的那部分做了一点特殊处理:
代码47行这里必须要对src1重新初始化为负无穷,因为经过上面的循环以后,src1都已经有值了,如果这里步重新初始化覆盖,就会导致前remainNram个元素是source1末尾的这部分元素,但是后面maxNum-remainNram这部分元素还是上一轮循环的结果,此时盲目去比较最大值,是无法得到前remainNram个元素的最大值的
代码53行也是类似,必须要重新初始化src1,要不然src1会有一部分负无穷
在这里插入图片描述

#include <bang.h>
#include <bang_device_functions.h>
#define EPS 1e-7
const int NRAM_MAX_SIZE = 1024 * 128;//后续树状求和必须保证NRAM_MAX_SIZE为2的幂次
const int maxNum = NRAM_MAX_SIZE/sizeof(float); //NRAM上最多存储maxNum个float元素
const int warpSize = 32;//__bang_reduce_sum每次从src取128字节数据相加,对应32个float元素,并且0-31的结果保存在索引0,32-63的结果保存在索引1

__nram__ float src1[maxNum];//每次搬运maxNum数据到NRAM
__nram__ float destSum[maxNum];//后面数值求和
__nram__ float destSumFinal[warpSize];//将destSum规约到destFinal[0]

__mlu_entry__ void softmaxKernel(float* dst, float* source1, float* globalMax, float* globalSum, int num) {
  
  int remain = num%taskDim;//如果不能整除,则让前部分taskId多处理一个元素
  int stepEasy = (num - remain)/taskDim;
  int stepHard = stepEasy + 1;
  int step = (taskId < remain ? stepHard : stepEasy);//前部分tsakId多处理一个元素
  int indStart = (taskId < remain ? taskId * stepHard : remain * stepHard + (taskId - remain) * stepEasy);
  int remainNram = step%maxNum;
  int repeat = step/maxNum;//如果一个task处理元素个数超出NRAM最大内存,则需要for循环
  //maxNum尽量取大一些,免得repeat过大导致求和过程累加过于严重,使得大数吃小数
  source1 = source1 + indStart;//设定起始偏移量

  //------------------------------------下面开始计算max
  __nram__ float tmp[maxNum];
  __nram__ float destOldMax;
  __nram__ float destNewMax;
  __bang_write_zero(destSum, maxNum);
  destNewMax = -INFINITY;//初始化为负无穷
  for(int i = 0; i < repeat; i++){
    __memcpy(src1, source1 + i * maxNum, maxNum * sizeof(float), GDRAM2NRAM);
    __bang_argmax(src1, src1, maxNum);//针对taskId处理的这step数据,借助于for循环把信息集中到长度为maxNum的向量src1中
    if(destNewMax < src1[0]){
      destNewMax = src1[0];//更新最大值
    }
    __bang_write_value(tmp, maxNum, -destNewMax);
    __bang_add(src1, tmp, src1, maxNum);//src1 = src1 - 最大值
    __bang_active_exp_less_0(src1, src1, maxNum);//src1 = exp(src1 - 最大值)
    if(i > 0){
      __bang_write_value(tmp, maxNum, exp(destOldMax - destNewMax));
      __bang_mul(destSum, destSum, tmp, maxNum);//destSum = destSum * exp(destOldMax - destNewMax)
    }
    __bang_add(destSum, destSum, src1, maxNum);//destSum = destSum + exp(src1 - destNewMax)
    destOldMax = destNewMax;
  }
  if(remainNram){
    __bang_write_value(src1, maxNum, -INFINITY);//必须要初始化src1全部元素为负无穷
    __memcpy(src1, source1 + repeat * maxNum, remainNram * sizeof(float), GDRAM2NRAM);
    __bang_argmax(src1, src1, maxNum);//针对taskId处理的这step数据,借助于for循环把信息集中到长度为maxNum的向量src1中
    if(destNewMax < src1[0]){
      destNewMax = src1[0];
    }
    __bang_write_value(src1, maxNum, destNewMax);//必须重新初始化为destNewMax
    __memcpy(src1, source1 + repeat * maxNum, remainNram * sizeof(float), GDRAM2NRAM);//必须再次读取
    __bang_write_value(tmp, maxNum, -destNewMax);
    __bang_add(src1, tmp, src1, maxNum);//后面maxNum-remainNram部分为0
    __bang_active_exp_less_0(src1, src1, maxNum);//相当于多加了maxNum-remainNram
    if(repeat > 0){
      __bang_write_value(tmp, maxNum, exp(destOldMax - destNewMax));
      __bang_mul(destSum, destSum, tmp, maxNum);
    }
    __bang_add(destSum, destSum, src1, maxNum);
    destOldMax = destNewMax;
  }//结束以后向量destNewMax保存了source1[indSart:indStart+step]这部分数据的全局最大值,destSum保存数值和
  //----------
  __bang_write_zero(destSumFinal, warpSize);//初始化destSumFinal全部元素为0
  int segNum = maxNum / warpSize;//将destSum分成segNum段,每段向量长度为warpSize,分段进行树状求和,segNum要求是2的幂次
  for(int strip = segNum/2; strip > 0; strip = strip / 2){//segNum要求是2的幂次即maxNum必须选取2的幂次
    for(int i = 0; i < strip ; i++){
      __bang_add(destSum + i * warpSize, destSum + i * warpSize, destSum + (i + strip) * warpSize, warpSize);
    } 
  }
  __bang_reduce_sum(destSumFinal, destSum, warpSize);
  destSumFinal[0] = destSumFinal[0] - (maxNum - remainNram);//把上面多加的(maxNum - remainNram)减掉
  
  //----------
  globalMax[0] = -INFINITY;
  globalSum[0] = 0.0;
  __sync_all();
  __bang_atomic_max(&destNewMax, globalMax, &destNewMax, 1);//globalMax[0]必须初始化为负无穷
  destSumFinal[0] = destSumFinal[0] * exp(destOldMax - globalMax[0]);
  //__bang_printf("taskId:%d, step:%d, sum:%.6f\n", taskId, step, destSumFinal[0]);
  __sync_all();
  __bang_atomic_add(destSumFinal, globalSum, destSumFinal, 1);//globalSum[0]必须初始化为0
  
  
  dst = dst + indStart;//设定起始偏移量
  float globalSumInv = 1.0/globalSum[0];
  
  for(int i = 0; i < repeat; i++){
    __bang_write_value(destSum, maxNum, -globalMax[0]);//初始化destSum全部元素为-globalMax[0]
    __memcpy(src1, source1 + i * maxNum, maxNum * sizeof(float), GDRAM2NRAM);
    __bang_add(src1, destSum, src1, maxNum);//src1 = src1 - globalMax[0] 
    __bang_active_exp_less_0(src1, src1, maxNum);//src1 = exp(src1 - globalMax[0])
    __bang_write_value(destSum, maxNum, globalSumInv);//初始化destSum全部元素为globalSum[0]Inv,使用1.0/globalSum[0]编译报错
    __bang_mul(src1, src1, destSum, maxNum);//倒数和另一个向量逐元素相乘得到除法结果
    __memcpy(dst + i * maxNum, src1, maxNum * sizeof(float), NRAM2GDRAM);
  }
  
  if(remainNram){ 
    __bang_write_value(src1, maxNum, -globalMax[0]);
    __bang_write_value(destSum, maxNum, -globalMax[0]);//初始化destSum全部元素为-globalMax[0]
    __memcpy(src1, source1 + repeat * maxNum, remainNram * sizeof(float), GDRAM2NRAM);
    __bang_add(src1, destSum, src1, maxNum);//src1 = src1 - globalMax[0] 
    __bang_active_exp_less_0(src1, src1, maxNum);//src1 = exp(src1 - globalMax[0])
    __bang_write_value(destSum, maxNum, globalSumInv);//初始化destSum全部元素为globalSum[0]Inv,使用1.0/globalSum[0]编译报错
    __bang_mul(src1, src1, destSum, maxNum);//倒数和另一个向量逐元素相乘得到除法结果
    __memcpy(dst + repeat * maxNum, src1, remainNram * sizeof(float), NRAM2GDRAM);
  }
  
  __bang_printf("taskId:%d,max:%.6f, sum:%.6f\n",taskId, globalMax[0], globalSum[0]);
  

}


int main(void)
{
  int num = 102001010;
  //int num = 11;
  cnrtQueue_t queue;
  CNRT_CHECK(cnrtSetDevice(0));
  CNRT_CHECK(cnrtQueueCreate(&queue));

  cnrtDim3_t dim = {4, 1, 1};
  int taskNum = dim.x * dim.y * dim.z;
  cnrtFunctionType_t ktype = CNRT_FUNC_TYPE_UNION1;

  cnrtNotifier_t start, end;
  CNRT_CHECK(cnrtNotifierCreate(&start));
  CNRT_CHECK(cnrtNotifierCreate(&end));

  float* host_dst = (float*)malloc(num * sizeof(float));
  float* host_src1 = (float*)malloc(num * sizeof(float));
  

  for (int i = 0; i < num; i++) {
    host_src1[i] = i%4;
  }

  float* mlu_dst;
  float* mlu_src1;
  float* globalMax;
  float* globalSum;
  CNRT_CHECK(cnrtMalloc((void**)&mlu_dst, num * sizeof(float)));
  CNRT_CHECK(cnrtMalloc((void**)&mlu_src1, num * sizeof(float)));
  CNRT_CHECK(cnrtMalloc((void**)&globalMax, sizeof(float)));
  CNRT_CHECK(cnrtMalloc((void**)&globalSum, sizeof(float)));

  CNRT_CHECK(cnrtMemcpy(mlu_src1, host_src1, num * sizeof(float), cnrtMemcpyHostToDev));
  
  //----------------------------
  CNRT_CHECK(cnrtPlaceNotifier(start, queue));
  softmaxKernel<<<dim, ktype, queue>>>(mlu_dst, mlu_src1, globalMax, globalSum, num);
  CNRT_CHECK(cnrtPlaceNotifier(end, queue));
  cnrtQueueSync(queue);

  //---------------------------
  CNRT_CHECK(cnrtMemcpy(host_dst, mlu_dst, num * sizeof(float), cnrtMemcpyDevToHost));
  for(int i = 0; i < 10; i++){
    printf("softmax[%d]:%.6e,origin:%.6f\n", i, host_dst[i], host_src1[i]);
  }
  float timeTotal;
  CNRT_CHECK(cnrtNotifierDuration(start, end, &timeTotal));
  printf("Total Time: %.3f ms\n", timeTotal / 1000.0);

  CNRT_CHECK(cnrtQueueDestroy(queue));

  cnrtFree(mlu_dst);
  cnrtFree(mlu_src1);
  cnrtFree(globalMax);
  cnrtFree(globalSum);
  
  free(host_dst);
  free(host_src1);
  

  return 0;
}




__bang_sub_scalar和__bang_mul_scalar的使用

经过查找,我们发现寒武纪里面有两个函数__bang_sub_scalar和__bang_mul_scalar可以直接实现向量减去一个常数以及向量×一个常数,为此我们可以删减中间变量tmp,加大maxNum,通过VS code的对比功能可以很容易发现两个代码之间的差距
在这里插入图片描述

#include <bang.h>
#include <bang_device_functions.h>
#define EPS 1e-7
const int NRAM_MAX_SIZE = 1024 * 256;//后续树状求和必须保证NRAM_MAX_SIZE为2的幂次
const int maxNum = NRAM_MAX_SIZE/sizeof(float); //NRAM上最多存储maxNum个float元素
const int warpSize = 32;//__bang_reduce_sum每次从src取128字节数据相加,对应32个float元素,并且0-31的结果保存在索引0,32-63的结果保存在索引1

__nram__ float src1[maxNum];//每次搬运maxNum数据到NRAM
__nram__ float destSum[maxNum];//后面数值求和
__nram__ float destSumFinal[warpSize];//将destSum规约到destFinal[0]

__mlu_entry__ void softmaxKernel(float* dst, float* source1, float* globalMax, float* globalSum, int num) {
  
  int remain = num%taskDim;//如果不能整除,则让前部分taskId多处理一个元素
  int stepEasy = (num - remain)/taskDim;
  int stepHard = stepEasy + 1;
  int step = (taskId < remain ? stepHard : stepEasy);//前部分taskId多处理一个元素
  int indStart = (taskId < remain ? taskId * stepHard : remain * stepHard + (taskId - remain) * stepEasy);
  int remainNram = step%maxNum;
  int repeat = step/maxNum;//如果一个task处理元素个数超出NRAM最大内存,则需要for循环
  //maxNum尽量取大一些,免得repeat过大导致求和过程累加过于严重,使得大数吃小数
  source1 = source1 + indStart;//设定起始偏移量

  //------------------------------------下面开始计算max
  __nram__ float destOldMax;
  __nram__ float destNewMax;
  __bang_write_zero(destSum, maxNum);
  destNewMax = -INFINITY;//初始化为负无穷
  for(int i = 0; i < repeat; i++){
    __memcpy(src1, source1 + i * maxNum, NRAM_MAX_SIZE, GDRAM2NRAM);
    __bang_argmax(src1, src1, maxNum);//针对taskId处理的这step数据,借助于for循环把信息集中到长度为maxNum的向量src1中
    if(destNewMax < src1[0]){
      destNewMax = src1[0];//更新最大值
    }
    __bang_sub_scalar(src1, src1, destNewMax, maxNum);//src1 = src1 - 最大值
    __bang_active_exp_less_0(src1, src1, maxNum);//src1 = exp(src1 - 最大值)
    if(i > 0){
      __bang_mul_scalar(destSum, destSum, exp(destOldMax - destNewMax), maxNum);//destSum = destSum * exp(destOldMax - destNewMax)
    }
    __bang_add(destSum, destSum, src1, maxNum);//destSum = destSum + exp(src1 - destNewMax)
    destOldMax = destNewMax;
  }
  if(remainNram){
    __bang_write_value(src1, maxNum, -INFINITY);//必须要初始化src1全部元素为负无穷
    __memcpy(src1, source1 + repeat * maxNum, remainNram * sizeof(float), GDRAM2NRAM);
    __bang_argmax(src1, src1, maxNum);//针对taskId处理的这step数据,借助于for循环把信息集中到长度为maxNum的向量src1中
    if(destNewMax < src1[0]){
      destNewMax = src1[0];
    }
    __bang_write_value(src1, maxNum, destNewMax);//必须重新初始化为destNewMax
    __memcpy(src1, source1 + repeat * maxNum, remainNram * sizeof(float), GDRAM2NRAM);//必须再次读取
    __bang_sub_scalar(src1, src1, destNewMax, maxNum);//后面maxNum-remainNram部分为0
    __bang_active_exp_less_0(src1, src1, maxNum);//相当于多加了maxNum-remainNram
    if(repeat > 0){
      __bang_mul_scalar(destSum, destSum, exp(destOldMax - destNewMax), maxNum);
    }
    __bang_add(destSum, destSum, src1, maxNum);
    destOldMax = destNewMax;
  }//结束以后向量destNewMax保存了source1[indSart:indStart+step]这部分数据的全局最大值,destSum保存数值和
  //----------
  __bang_write_zero(destSumFinal, warpSize);//初始化destSumFinal全部元素为0
  int segNum = maxNum / warpSize;//将destSum分成segNum段,每段向量长度为warpSize,分段进行树状求和,segNum要求是2的幂次
  for(int strip = segNum/2; strip > 0; strip = strip / 2){//segNum要求是2的幂次即maxNum必须选取2的幂次
    for(int i = 0; i < strip ; i++){
      __bang_add(destSum + i * warpSize, destSum + i * warpSize, destSum + (i + strip) * warpSize, warpSize);
    } 
  }
  __bang_reduce_sum(destSumFinal, destSum, warpSize);
  destSumFinal[0] = destSumFinal[0] - (maxNum - remainNram);//把上面多加的(maxNum - remainNram)减掉
  
  //----------
  globalMax[0] = -INFINITY;
  globalSum[0] = 0.0;
  __sync_all();
  __bang_atomic_max(&destNewMax, globalMax, &destNewMax, 1);//globalMax[0]必须初始化为负无穷
  destSumFinal[0] = destSumFinal[0] * exp(destOldMax - globalMax[0]);
  //__bang_printf("taskId:%d, step:%d, sum:%.6f\n", taskId, step, destSumFinal[0]);
  __sync_all();
  __bang_atomic_add(destSumFinal, globalSum, destSumFinal, 1);//globalSum[0]必须初始化为0
  
  
  dst = dst + indStart;//设定起始偏移量
  float globalSumInv = 1.0/globalSum[0];
  
  for(int i = 0; i < repeat; i++){
    __memcpy(src1, source1 + i * maxNum, NRAM_MAX_SIZE, GDRAM2NRAM);
    __bang_sub_scalar(src1, src1, globalMax[0], maxNum);//src1 = src1 - globalMax[0] 
    __bang_active_exp_less_0(src1, src1, maxNum);//src1 = exp(src1 - globalMax[0])
    __bang_mul_scalar(src1, src1, globalSumInv, maxNum);//倒数和另一个向量逐元素相乘得到除法结果
    __memcpy(dst + i * maxNum, src1, NRAM_MAX_SIZE, NRAM2GDRAM);
  }
  
  if(remainNram){ 
    __bang_write_value(src1, maxNum, -globalMax[0]);
    __memcpy(src1, source1 + repeat * maxNum, remainNram * sizeof(float), GDRAM2NRAM);
    __bang_sub_scalar(src1, src1, globalMax[0], maxNum);//src1 = src1 - globalMax[0] 
    __bang_active_exp_less_0(src1, src1, maxNum);//src1 = exp(src1 - globalMax[0])
    __bang_mul_scalar(src1, src1, globalSumInv, maxNum);//倒数和另一个向量逐元素相乘得到除法结果
    __memcpy(dst + repeat * maxNum, src1, remainNram * sizeof(float), NRAM2GDRAM);
  }
  
  __bang_printf("taskId:%d,repeat:%d,max:%.6f, sum:%.6f\n",taskId, repeat, globalMax[0], globalSum[0]);
  

}


int main(void)
{
  int num = 102001010;
  //int num = 11;
  cnrtQueue_t queue;
  CNRT_CHECK(cnrtSetDevice(0));
  CNRT_CHECK(cnrtQueueCreate(&queue));

  cnrtDim3_t dim = {4, 1, 1};
  int taskNum = dim.x * dim.y * dim.z;
  cnrtFunctionType_t ktype = CNRT_FUNC_TYPE_UNION1;

  cnrtNotifier_t start, end;
  CNRT_CHECK(cnrtNotifierCreate(&start));
  CNRT_CHECK(cnrtNotifierCreate(&end));

  float* host_dst = (float*)malloc(num * sizeof(float));
  float* host_src1 = (float*)malloc(num * sizeof(float));
  

  for (int i = 0; i < num; i++) {
    host_src1[i] = i%4;
  }

  float* mlu_dst;
  float* mlu_src1;
  float* globalMax;
  float* globalSum;
  CNRT_CHECK(cnrtMalloc((void**)&mlu_dst, num * sizeof(float)));
  CNRT_CHECK(cnrtMalloc((void**)&mlu_src1, num * sizeof(float)));
  CNRT_CHECK(cnrtMalloc((void**)&globalMax, sizeof(float)));
  CNRT_CHECK(cnrtMalloc((void**)&globalSum, sizeof(float)));

  CNRT_CHECK(cnrtMemcpy(mlu_src1, host_src1, num * sizeof(float), cnrtMemcpyHostToDev));
  
  //----------------------------
  CNRT_CHECK(cnrtPlaceNotifier(start, queue));
  softmaxKernel<<<dim, ktype, queue>>>(mlu_dst, mlu_src1, globalMax, globalSum, num);
  CNRT_CHECK(cnrtPlaceNotifier(end, queue));
  cnrtQueueSync(queue);

  //---------------------------
  CNRT_CHECK(cnrtMemcpy(host_dst, mlu_dst, num * sizeof(float), cnrtMemcpyDevToHost));
  for(int i = 0; i < 10; i++){
    printf("softmax[%d]:%.6e,origin:%.6f\n", i, host_dst[i], host_src1[i]);
  }
  float timeTotal;
  CNRT_CHECK(cnrtNotifierDuration(start, end, &timeTotal));
  printf("Total Time: %.3f ms\n", timeTotal / 1000.0);

  CNRT_CHECK(cnrtQueueDestroy(queue));

  cnrtFree(mlu_dst);
  cnrtFree(mlu_src1);
  cnrtFree(globalMax);
  cnrtFree(globalSum);
  
  free(host_dst);
  free(host_src1);
  

  return 0;
}




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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

如何设置电脑桌面提醒,电脑笔记软件哪个好?

对于大多数上班族来说&#xff0c;每天要完成的待办事项实在太多了&#xff0c;如果不能及时去处理&#xff0c;很容易因为各种因素导致忘记&#xff0c;从而给自己带来不少麻烦。所以&#xff0c;我们往往会借助一些提醒类的软件将各项任务逐一记录下来&#xff0c;然后设置上…

时序分解 | Matlab实现CPO-VMD基于冠豪猪优化算法(CPO)优化VMD变分模态分解时间序列信号分解

时序分解 | Matlab实现CPO-VMD基于冠豪猪优化算法(CPO)优化VMD变分模态分解时间序列信号分解 目录 时序分解 | Matlab实现CPO-VMD基于冠豪猪优化算法(CPO)优化VMD变分模态分解时间序列信号分解效果一览基本介绍程序设计参考资料 效果一览 基本介绍 【原创】CPO-VMD【24年新算法…

Mysql系列-1.Mysql基本使用

&#x1f44f;作者简介&#xff1a;大家好&#xff0c;我是爱吃芝士的土豆倪&#xff0c;24届校招生Java选手&#xff0c;很高兴认识大家&#x1f4d5;系列专栏&#xff1a;Spring源码、JUC源码、Kafka原理、分布式技术原理、数据库技术&#x1f525;如果感觉博主的文章还不错的…

【揭秘APT攻击】——内网渗透实战攻略,带你领略网络安全的绝密世界!

&#x1f308;个人主页: Aileen_0v0 &#x1f525;热门专栏: 华为鸿蒙系统学习|计算机网络|数据结构与算法 &#x1f4ab;个人格言:"没有罗马,那就自己创造罗马~" 目录 介绍 什么是内网&#xff1f; 什么是内网渗透&#xff1f; 内网渗透的目的&#xff1a; 内网…

leetcode:1716. 计算力扣银行的钱(python3解法)

难度&#xff1a;简单 Hercy 想要为购买第一辆车存钱。他 每天 都往力扣银行里存钱。 最开始&#xff0c;他在周一的时候存入 1 块钱。从周二到周日&#xff0c;他每天都比前一天多存入 1 块钱。在接下来每一个周一&#xff0c;他都会比 前一个周一 多存入 1 块钱。 给你 n &am…

微服务架构RabbitMQ实现CQRS模式

在现代软件开发中,微服务架构和CQRS模式都是备受关注的技术趋势。微服务架构通过将应用程序拆分为一系列小型、自治的服务,提供了更好的可伸缩性和灵活性。而CQRS模式则通过将读操作和写操作分离,优化了系统的性能和可维护性。本文小编将为大家介绍如何在ASP.NET Core微服务…

机器学习中的隐马尔可夫模型及Python实现示例

隐马尔可夫模型&#xff08;HMM&#xff09;是一种统计模型&#xff0c;用于描述观测序列和隐藏状态序列之间的概率关系。它通常用于生成观测值的底层系统或过程未知或隐藏的情况&#xff0c;因此它被称为“隐马尔可夫模型”。 它用于根据生成数据的潜在隐藏过程来预测未来的观…

第三次面试总结 - 吉云集团 - 全栈开发

&#x1f9f8;欢迎来到dream_ready的博客&#xff0c;&#x1f4dc;相信您对专栏 “本人真实面经” 很感兴趣o (ˉ▽ˉ&#xff1b;) 专栏 —— 本人真实面经&#xff0c;更多真实面试经验&#xff0c;中大厂面试总结等您挖掘 目录 总结&#xff08;非详细&#xff09; 面试内…

AIGC无人直播系统技术

随着信息技术的快速发展和互联网的普及&#xff0c;直播行业迎来了蓬勃发展的机遇。然而&#xff0c;传统的直播方式存在一些局限性&#xff0c;如场地限制、设备携带不便等问题。为了解决这些问题&#xff0c;AIGC推出了一项创新性的无人直播系统技术。 AIGC无人直播系统技术…

FMEA的定义以及应用目标——SunFMEA软件

故障模式与影响分析&#xff08;Failure Modes and Effects Analysis&#xff0c;简称FMEA&#xff09;是一种预防性的质量工具&#xff0c;用于识别和评估产品设计、生产和使用过程中可能出现的故障模式及其对系统性能的影响。通过对故障模式的系统化分析和评估&#xff0c;FM…

​iOS 应用上架指南:资料填写及提交审核

目录 摘要 引言 打开appuploader工具&#xff0c;第二步&#xff1a;打开appuploader工具 第五步&#xff1a;交付应用程序&#xff0c;在iTunes Connect中查看应用程序 总结 摘要 本文提供了iOS新站上架资料填写及提交审核的详细指南&#xff0c;包括创建应用、资料填写-…

震惊!居然有人给 Raspberry Pi 5 做 X 射线!

Jeff Geerling 会做一些莫名其妙的怪事&#xff0c;比如用信鸽来对抗网速&#xff0c;不过这也是我们喜欢他视频的原因。最近&#xff0c;杰夫对 Raspberry Pi 5 进行了 X 光透视&#xff0c;揭示了 Raspberry Pi 5 最新、最强大的计算机内部的秘密。 震惊&#xff01;居然有人…

软通测试岗面试内部资料

基础性问题 1.你对加班怎么看 2你的优势有哪些 3.你的缺点是什么 4.最有成就感的事情是什么 5.你的职业规划是什么 6离职原因是什么 7.还有什么想问我们的吗 8.你有其他的 offer 吗 专业性问题 9.HTTPS 和 HTTP 的区别 10.HTTPS 的工作原理 11.客户端在使用 HTTPS …

认识Linux指令 “zip/unzip” 指令

01.zip/unzip指令 语法&#xff1a; zip 压缩文件.zip 目录或文件 功能&#xff1a; 将目录或文件压缩成zip格式 常用选项&#xff1a; -r 递归处理&#xff0c;将指定目录下的所有文件和子目录一并处理 举例 将test2目录压缩&#xff1a;zip test2.zip test2/* 解压到…

CUTANA™ pAG-Tn5 for CUTTag

CUTANA pAG-Tn5是靶向剪切及转座酶(CUT&Tag)技术中进行高效绘制染色质特征的关键试剂。与ChIP-seq相比&#xff0c;CUT&Tag在降低细胞需求量和测序深度的信噪比方面进行了显著改进。CUTANA pAG-Tn5是一种高活性的E. coli转座酶突变体(Tn5)与蛋白A/G的融合产物&#xff…

Windows11快速安装Android子系统

很多小伙伴想在电脑运行一下安卓程序&#xff0c;或则上班用手机摸鱼不方便&#xff0c;用电脑又没有想要的手机软件&#xff0c;那么怎么用电脑来安装安卓软件呢&#xff1f; 首先设置地区 安装Android子系统的前提需要安装 Amazon Appstore 这个应用&#xff0c;内地不能下载…

【深入浅出JVM原理及调优】「搭建理论知识框架」全方位带你深入探索类加载机制

全方位带你深入探索类加载机制 专栏介绍前提准备面向人群知识脉络类加载是什么类加载和Class类对象的关系JVM的预加载机制加载class文件的方式 类加载过程&#xff08;类的生命周期&#xff09;加载阶段生成对应的Class文件 连接操作验证&#xff08;确保被加载的类的正确性&am…

Aop编程之动态代理

1、Java代理介绍 Java中的代理方式包括静态代理和动态代理。 静态代理在编译期间就确定了代理对象&#xff0c;动态代理是在运行期间动态生成代理对象。动态代理包括cglib动态代理和jdk动态代理&#xff0c;在目标对象有接口的情况下&#xff0c;可以使用jdk动态代理或者cglib…

使用numpy处理图片——基础操作

大纲 准备工作图片像素大小修改透明度 numpy是一款非常优秀的处理多维数组的Python基础包。在现实中&#xff0c;我们最经常接触的多维数组相关的场景就是图像处理。本系列将通过若干篇对图像处理相关的探讨&#xff0c;来介绍numpy的使用方法&#xff0c;以获得直观的体验。 本…

element-plus里el-date-picker日期选择器,默认值不显示的问题

官网文档给出的示例默认值也是没有没显示的。 找了很多方法&#xff0c;最终是给v-model"defaultTime"绑定初始值&#xff0c;如下代码&#xff0c;需要的可以改一下 <el-date-picker class"top_select" v-model"defaultTime" type"da…