昇腾社区首页
中文
注册

aclnnMoeGatingTopK

支持的产品型号

  • Atlas A2 训练系列产品/Atlas 800I A2 推理产品/A200I A2 Box 异构组件
  • Atlas A3 训练系列产品/Atlas A3 推理系列产品

功能说明

  • 算子功能:MoE计算中,对输入x做Sigmoid计算,对计算结果分组进行排序,最后根据分组排序的结果选取前k个专家。

  • 计算公式:

    对输入做sigmoid:

    normOut=sigmoid(x)normOut=sigmoid(x)

    如果bias不为空:

    normOut=normOut+biasnormOut = normOut + bias

    对计算结果按照groupCount进行分组,每组按照topk2的sum值对group进行排序,取前kGroup个组:

    groupOut,groupId=TopK(ReduceSum(TopK(Split(normOut,groupCount),k=2,dim=1),dim=1),k=kGroup)groupOut, groupId = TopK(ReduceSum(TopK(Split(normOut, groupCount), k=2, dim=-1), dim=-1),k=kGroup)

    根据上一步的groupId获取normOut中对应的元素,将数据再做TopK,得到expertIdxOut的结果:

    y,expertIdxOut=TopK(normOut[groupId,:],k=k)y,expertIdxOut=TopK(normOut[groupId, :],k=k)

    对y按照输入的routedScalingFactor和eps参数进行计算,得到yOut的结果:

    yOut=y/(ReduceSum(y,dim=1)+eps)routedScalingFactoryOut = y / (ReduceSum(y, dim=-1)+eps)*routedScalingFactor

函数原型

每个算子分为两段式接口,必须先调用“aclnnMoeGatingTopKGetWorkspaceSize”接口获取计算所需workspace大小以及包含了算子计算流程的执行器,再调用“aclnnMoeGatingTopK”接口执行计算。

  • aclnnStatus aclnnMoeGatingTopKGetWorkspaceSize(const aclTensor *x, const aclTensor *biasOptional, int64_t k, int64_t kGroup, int64_t groupCount, int64_t groupSelectMode, int64_t renorm, int64_t normType, bool outFlag, double routedScalingFactor, double eps, const aclTensor *yOut, const aclTensor *expertIdxOut, const aclTensor *outOut, uint64_t *workspaceSize, aclOpExecutor **executor)
  • aclnnStatus aclnnMoeGatingTopK(void* workspace, uint64_t workspaceSize, aclOpExecutor* executor, aclrtStream stream)

aclnnMoeGatingTopKGetWorkspaceSize

  • 参数说明:

    • x(aclTensor*,计算输入):Device侧的aclTensor,待计算的输入,要求是一个2D的Tensor,数据类型支持FLOAT16、BFLOAT16、FLOAT32,数据格式要求为ND,支持非连续的Tensor
      • Atlas A2 训练系列产品/Atlas 800I A2 推理产品/A200I A2 Box 异构组件Atlas A3 训练系列产品/Atlas A3 推理系列产品:x最后一维的大小(即专家数)当前只支持256。
    • biasOptional(aclTensor*,可选计算输入):Device侧的aclTensor,与输入x进行计算的bias值,要求是一个1D的Tensor,数据类型支持FLOAT16、BFLOAT16、FLOAT32,数据类型与x需要保持一致,数据格式要求为ND,支持非连续的Tensor,要求biasOptional的shape值与x的最后一维相等。
      • Atlas A2 训练系列产品/Atlas 800I A2 推理产品/A200I A2 Box 异构组件Atlas A3 训练系列产品/Atlas A3 推理系列产品:必须不为None。
    • k(int64_t,计算输入):topk的k值。
      • Atlas A2 训练系列产品/Atlas 800I A2 推理产品/A200I A2 Box 异构组件Atlas A3 训练系列产品/Atlas A3 推理系列产品:取值范围为[1,32]。
    • kGroup(int64_t,计算输入):分组排序后取的group个数。
      • Atlas A2 训练系列产品/Atlas 800I A2 推理产品/A200I A2 Box 异构组件Atlas A3 训练系列产品/Atlas A3 推理系列产品:当前仅支持取4。
    • groupCount(int64_t,计算输入):分组的总个数。
      • Atlas A2 训练系列产品/Atlas 800I A2 推理产品/A200I A2 Box 异构组件Atlas A3 训练系列产品/Atlas A3 推理系列产品:当前仅支持取8。
    • groupSelectMode(int64_t,计算输入):分组排序的方式,当前仅支持1,表示使用topk2的sum值对group进行排序。
    • renorm(int64_t,计算输入):renorm标记,当前仅支持0,表示先进行norm操作,再计算topk。
    • normType(int64_t,计算输入):norm函数类型,当前仅支持1,表示使用Sigmoid函数。
    • outFlag(bool,计算输入):表示是否输出norm操作的结果,当前仅支持false,表示不输出。
    • routedScalingFactor(double,计算输入):用于计算yOut使用的routedScalingFactor系数。
    • eps(double,计算输入):用于计算yOut使用的eps系数。
    • yOut(aclTensor*,计算输出):Device侧的aclTensor,对x做norm操作,分组排序topk后计算的结果,要求是一个2D的Tensor,数据类型支持FLOAT16、BFLOAT16、FLOAT32,数据类型与x需要保持一致,数据格式要求为ND,不支持非连续的Tensor,第一维的大小要求与x的第一维相同,最后一维的大小与k相同。
    • expertIdxOut(aclTensor*,计算输出):Device侧的aclTensor,对x做norm操作,分组排序topk后的索引,即专家的序号,shape要求与yOut一致,数据类型支持INT32,数据格式要求为ND,不支持非连续的Tensor
    • outOut(aclTensor*,计算输出):Device侧的aclTensor,norm计算的输出结果,通过outFlag参数控制是否生效,shape要求与x保持一致,数据类型支持FLOAT32,数据格式要求为ND,不支持非连续的Tensor
    • workspaceSize(uint64_t*,出参):Device侧的整型,返回需要在Device侧申请的workspace大小。
    • executor(aclOpExecutor**,出参):Device侧的aclOpExecutor,返回op执行器,包含了算子计算流程。
  • 返回值:

    返回aclnnStatus状态码,具体参见aclnn返回码

    161001(ACLNN_ERR_PARAM_NULLPTR): 1. 计算输入和计算输出是空指针。
    161002(ACLNN_ERR_PARAM_INVALID): 1. 输入和输出的数据类型不在支持的范围内。
    561002(ACLNN_ERR_INNER_TILING_ERROR): 1. x的shape不满足要求。
                                          2. x和biasOptional的shape不匹配。
                                          3. k的大小不在1到x_shape[-1] / groupCount * kGroup之间。
                                          4. kGroup的大小不在1到groupCount之间。
                                          5. 每个group的专家数按32对齐之后 * groupCount超过2048
                                          6. 计算输入参数的值不满足要求。

aclnnMoeGatingTopK

  • 参数说明:

    • workspace(void*,入参):在Device侧申请的workspace内存地址。
    • workspaceSize(uint64_t,入参):在Device侧申请的workspace大小,由第一段接口aclnnMoeGatingTopKGetWorkspaceSize获取。
    • executor(aclOpExecutor*,入参):op执行器,包含了算子计算流程。
    • stream(aclrtStream,入参):指定执行任务的AscendCL stream流。
  • 返回值:

    返回aclnnStatus状态码,具体参见aclnn返回码

约束说明

  • Atlas A2 训练系列产品/Atlas 800I A2 推理产品/A200I A2 Box 异构组件Atlas A3 训练系列产品/Atlas A3 推理系列产品:专家个数为256。

调用示例

示例代码如下,仅供参考,具体编译和执行过程请参考编译与运行样例

#include "acl/acl.h"
#include "aclnnop/aclnn_moe_gating_top_k.h"
#include <iostream>
#include <vector>
#include <random>

#define CHECK_RET(cond, return_expr) \
  do {                               \
    if (!(cond)) {                   \
      return_expr;                   \
    }                                \
  } while (0)

#define LOG_PRINT(message, ...)     \
  do {                              \
    printf(message, ##__VA_ARGS__); \
  } while (0)

int64_t GetShapeSize(const std::vector<int64_t>& shape) {
  int64_t shape_size = 1;
  for (auto i : shape) {
    shape_size *= i;
  }
  return shape_size;
}

std::vector<float> GenerateRandomFloats(int64_t count) {
    std::random_device rd;
    std::mt19937 gen(rd());
    std::uniform_real_distribution<float> dist(0.0f, 10.0f);

    std::vector<float> result(count);
    for (auto& num : result) {
        num = dist(gen);
    }
    return result;
}
int Init(int32_t deviceId, aclrtStream* stream) {
  // 固定写法,AscendCL初始化
  auto  ret = aclInit(nullptr);
  CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclInit failed. ERROR: %d\n", ret); return ret);
  ret = aclrtSetDevice(deviceId);
  CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclrtSetDevice failed. ERROR: %d\n", ret); return ret);
  ret = aclrtCreateStream(stream);
  CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclrtCreateStream failed. ERROR: %d\n", ret); return ret);
  return 0;
}

template <typename T>
int CreateAclTensor(const std::vector<T>& hostData, const std::vector<int64_t>& shape, void** deviceAddr,
                    aclDataType dataType, aclTensor** tensor) {
  auto size = GetShapeSize(shape) * sizeof(T);
  // 调用aclrtMalloc申请device侧内存
  auto ret = aclrtMalloc(deviceAddr, size, ACL_MEM_MALLOC_HUGE_FIRST);
  CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclrtMalloc failed. ERROR: %d\n", ret); return ret);
  // 调用aclrtMemcpy将host侧数据拷贝到device侧内存上
  ret = aclrtMemcpy(*deviceAddr, size, hostData.data(), size, ACL_MEMCPY_HOST_TO_DEVICE);
  CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclrtMemcpy failed. ERROR: %d\n", ret); return ret);
  // 计算连续tensor的strides
  std::vector<int64_t> strides(shape.size(), 1);
  for (int64_t i = shape.size() - 2; i >= 0; i--) {
    strides[i] = shape[i + 1] * strides[i + 1];
  }
  // 调用aclCreateTensor接口创建aclTensor
  *tensor = aclCreateTensor(shape.data(), shape.size(), dataType, strides.data(), 0, aclFormat::ACL_FORMAT_ND,
                            shape.data(), shape.size(), *deviceAddr);
  return 0;
}

int main() {
  // 1. (固定写法)device/stream初始化, 参考AscendCL对外接口列表
  // 根据自己的实际device填写deviceId
  int32_t deviceId = 0;
  aclrtStream stream;
  auto ret = Init(deviceId, &stream);
  // check根据自己的需要处理
  CHECK_RET(ret == 0, LOG_PRINT("Init acl failed. ERROR: %d\n", ret); return ret);

  // 2. 构造输入与输出,需要根据API的接口自定义构造
  std::vector<int64_t> inputShape = {3, 256};
  std::vector<int64_t> biasShape = {256};
  std::vector<int64_t> outShape = {3, 8};
  std::vector<int64_t> expertIdOutShape = {3, 8};
  std::vector<int64_t> normOutShape = {3, 256};

  void* inputAddr = nullptr;
  void* biasAddr = nullptr;
  void* outAddr = nullptr;
  void* expertIdOutAddr = nullptr;
  void* normOutAddr = nullptr;
  
  aclTensor* input = nullptr;
  aclTensor* bias = nullptr;
  aclTensor* out = nullptr;
  aclTensor* expertIdOut = nullptr;
  aclTensor* normOut = nullptr;
  
  std::vector<float> inputHostData = GenerateRandomFloats(GetShapeSize(inputShape));
  std::vector<float> biasHostData = GenerateRandomFloats(GetShapeSize(biasShape));
  std::vector<float> outHostData(GetShapeSize(outShape));
  std::vector<int32_t> expertIdOutHostData(GetShapeSize(expertIdOutShape));
  std::vector<float> normOutHostData(GetShapeSize(normOutShape));

  // 创建expandedPermutedRows aclTensor
  ret = CreateAclTensor(inputHostData, inputShape, &inputAddr, aclDataType::ACL_FLOAT, &input);
  CHECK_RET(ret == ACL_SUCCESS, return ret);
  // 创建expandedPermutedRows aclTensor
  ret = CreateAclTensor(biasHostData, biasShape, &biasAddr, aclDataType::ACL_FLOAT, &bias);
  CHECK_RET(ret == ACL_SUCCESS, return ret);
  // 创建expertForSourceRow aclTensor
  ret = CreateAclTensor(outHostData, outShape, &outAddr, aclDataType::ACL_FLOAT, &out);
  CHECK_RET(ret == ACL_SUCCESS, return ret);
  // 创建expandedSrcToDstRow aclTensor
  ret = CreateAclTensor(expertIdOutHostData, expertIdOutShape, &expertIdOutAddr, aclDataType::ACL_INT32, &expertIdOut);
  CHECK_RET(ret == ACL_SUCCESS, return ret);
  // 创建expandedSrcToDstRow aclTensor
  ret = CreateAclTensor(normOutHostData, normOutShape, &normOutAddr, aclDataType::ACL_FLOAT, &normOut);
  CHECK_RET(ret == ACL_SUCCESS, return ret);

  // 3.调用CANN算子库API,需要修改为具体的算子接口
  uint64_t workspaceSize = 0;
  aclOpExecutor* executor;

  // 调用aclnnMoeGatingTopK第一段接口
  ret = aclnnMoeGatingTopKGetWorkspaceSize(input, bias, 8, 4, 8, 1, 0, 1, false, 1, 1, out, expertIdOut, normOut, &workspaceSize, &executor);
  CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclnnMoeGatingTopKGetWorkspaceSize failed. ERROR: %d\n", ret); return ret);
  // 根据第一段接口计算出的workspaceSize申请device内存
  void* workspaceAddr = nullptr;
  if (workspaceSize > 0) {
    ret = aclrtMalloc(&workspaceAddr, workspaceSize, ACL_MEM_MALLOC_HUGE_FIRST);
    CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("allocate workspace failed. ERROR: %d\n", ret); return ret;);
  }
  // 调用aclnnMoeGatingTopK第二段接口
  ret = aclnnMoeGatingTopK(workspaceAddr, workspaceSize, executor, stream);
  CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclnnMoeGatingTopK failed. ERROR: %d\n", ret); return ret);

  // 4.( 固定写法)同步等待任务执行结束
  ret = aclrtSynchronizeStream(stream);
  CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclrtSynchronizeStream failed. ERROR: %d\n", ret); return ret);

  // 5. 获取输出的值,将device侧内存上的结果拷贝至Host侧,需要根据具体API的接口定义修改
  auto size = GetShapeSize(outShape);
  std::vector<float> resultData(size, 0.0f);
  ret = aclrtMemcpy(resultData.data(), resultData.size() * sizeof(resultData[0]),
                    outAddr, size * sizeof(float), ACL_MEMCPY_DEVICE_TO_HOST);
  CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("copy result from device to host failed. ERROR: %d\n", ret); return ret);
  for (int64_t i = 0; i < size; i++) {
    LOG_PRINT("result[%ld] is: %f\n", i, resultData[i]);
  }

  // 6. 释放aclTensor和aclScalar,需要根据具体API的接口定义修改
  aclDestroyTensor(input);
  aclDestroyTensor(bias);
  aclDestroyTensor(out);
  aclDestroyTensor(expertIdOut);
  aclDestroyTensor(normOut);

  // 7. 释放device资源,需要根据具体API的接口定义修改
  aclrtFree(inputAddr);
  aclrtFree(biasAddr);
  aclrtFree(outAddr);
  aclrtFree(normOutAddr);
  aclrtFree(expertIdOutAddr);
  if (workspaceSize > 0) {
    aclrtFree(workspaceAddr);
  }
  aclrtDestroyStream(stream);
  aclrtResetDevice(deviceId);
  aclFinalize();
  return 0;
}