DSP开发流程

整体框架

在地平线提供的Softmax算子示例中,展示了如何通过调度UCP框架实现DSP自定义算子的功能封装,您可以在OE包中的samples/ucp_tutorial/custom_operator/dsp_sample处获取示例源码进行同步阅读理解。

P3develop

Softmax算子开发

本章节主要以DSP Softmax算子开发为例为您介绍DSP算子开发的流程,对应地平线OE包中的samples/ucp_tutorial/custom_operator/dsp_sample/dsp_code/softmax/softmax_ivp.cc部分。

完整的算子开发分为如下三个步骤:

  1. UCP API的调用,主要负责任务发起及计算资源分配;

  2. DSP算子开发;

  3. DSP算子注册运行。

Softmax分析

Softmax算子可以拆分为以下四个基础计算:

  1. 计算输入元素中的最大值max。

  2. 计算并更新输入的每个元素: input = exp(input - max) 。

  3. 计算更新后input的和sum。

  4. 计算 output = input / sum 。

UCP API

在此部分,将为您展示如何使用UCP接口申请内存资源、发起任务。以Softmax示例为例,由于UCP接口只支持输入输出这两个参数,如您需要将其它参数传递到DSP端,则需要进行一层封装,将封装后的“输入”传入接口,再由DSP从封装的“输入”中获取参数。

如下示例中,封装“输入”中包括三个字段,data_size为数据尺寸,示例中取100000,input字段为发送给dsp的数据地址,run_tcm_opt字段表示是否使用tcm优化实现。

// softmax op param
typedef struct {
  int32_t data_size;
  uint64_t input;
  uint8_t run_tcm_opt;
} hbDSPSoftmaxParam;

// #define DEBUG_RESULT

int32_t test_softmax_op(int32_t argc, char **argv) {
  LOGI("softmax op begin");

  int32_t size = 100000;
  int32_t data_size = size * sizeof(float32_t);

  hbUCPSysMem input_mem, output_mem, output_opt_mem;

  // prepare input data
  hbUCPSysMem input_data;
  HB_CHECK_SUCCESS(hbUCPMalloc(&input_data, data_size, 0),
                   "hbUCPMalloc input_data failed");
  HB_CHECK_SUCCESS(hbDSPAddrMap(&input_data, &input_data),
                   "Failed to hbDSPAddrMap input_data");

  float32_t *data_ptr = static_cast<float32_t *>(input_data.virAddr);
  for (uint32_t i = 0U; i < size; ++i) {
    data_ptr[i] = 1 + i / float32_t(size);
  }

  HB_CHECK_SUCCESS(hbUCPMalloc(&output_mem, data_size, 0),
                   "hbUCPMalloc output_mem failed");
  HB_CHECK_SUCCESS(hbDSPAddrMap(&output_mem, &output_mem),
                   "Failed to hbDSPAddrMap output_mem");
  HB_CHECK_SUCCESS(hbUCPMalloc(&output_opt_mem, data_size, 0),
                   "hbUCPMalloc output_opt_mem failed");
  HB_CHECK_SUCCESS(hbDSPAddrMap(&output_opt_mem, &output_opt_mem),
                   "Failed to hbDSPAddrMap output_opt_mem");
  HB_CHECK_SUCCESS(hbUCPMalloc(&input_mem, sizeof(hbDSPSoftmaxParam), 0),
                   "hbUCPMalloc input_mem failed");
  HB_CHECK_SUCCESS(hbDSPAddrMap(&input_mem, &input_mem),
                   "Failed to hbDSPAddrMap input_mem");
  hbDSPSoftmaxParam *ptr = static_cast<hbDSPSoftmaxParam *>(input_mem.virAddr);
  ptr->data_size = data_size;
  ptr->input = input_data.phyAddr;
  ptr->run_tcm_opt = 0;

  // ctrl param
  hbUCPSchedParam sched_param;
  HB_UCP_INITIALIZE_SCHED_PARAM(&sched_param);
  sched_param.backend = HB_UCP_DSP_CORE_0;
  sched_param.priority = 0;

  // run without ping-pong
  hbUCPTaskHandle_t task{nullptr};
  HB_CHECK_SUCCESS(hbDSPRpcV2(&task, &input_mem, &output_mem, 0x1402),
                   "hbDSPRpcV2 failed");
  HB_CHECK_SUCCESS(hbUCPSubmitTask(task, &sched_param), "hbUCPSubmitTask failed");
  HB_CHECK_SUCCESS(hbUCPWaitTaskDone(task, 0), "hbUCPWaitTaskDone failed");
  HB_CHECK_SUCCESS(hbUCPReleaseTask(task), "hbUCPReleaseTask failed");

  // run without ping-pong
  ptr->run_tcm_opt = 1;
  task = nullptr;
  HB_CHECK_SUCCESS(hbDSPRpcV2(&task, &input_mem, &output_opt_mem, 0x1402),
                   "hbDSPRpcV2 failed");
  HB_CHECK_SUCCESS(hbUCPSubmitTask(task, &sched_param), "hbUCPSubmitTask failed");
  HB_CHECK_SUCCESS(hbUCPWaitTaskDone(task, 0), "hbUCPWaitTaskDone failed");
  HB_CHECK_SUCCESS(hbUCPReleaseTask(task), "hbUCPReleaseTask failed");

  float32_t *dst_data_ptr = static_cast<float32_t *>(output_mem.virAddr);
  float32_t *dst_opt_data_ptr =
      static_cast<float32_t *>(output_opt_mem.virAddr);
  bool flag{true};
  for (uint32_t i = 0U; i < size; ++i) {
    if (std::abs(dst_data_ptr[i] - dst_opt_data_ptr[i]) /
            std::abs(dst_data_ptr[i]) >
        1e-6) {
      LOGE("opt vs non-opt result is diff {}, dst {} opt {}", i,
           dst_data_ptr[i], dst_opt_data_ptr[i]);
      flag = false;
      break;
    }
  }
  if (flag) {
    LOGI("opt vs non-opt result is same");
  }

  hbDSPAddrUnmap(&input_mem);
  hbUCPFree(&input_mem);
  hbDSPAddrUnmap(&output_mem);
  hbUCPFree(&output_mem);
  hbDSPAddrUnmap(&output_opt_mem);
  hbUCPFree(&output_opt_mem);
  hbDSPAddrUnmap(&input_data);
  hbUCPFree(&input_data);
  LOGI("softmax op finish");
  return 0;
}

DSP算子实现

本章节将对如何实现上一节中提到的四个基础运算从而实现DSP Softmax算子进行介绍。

Cadence实现了一些基础数学运算,方便您进行开发。您可以从Cadence的基础示例中获取源码,也可从地平线直接获取编译好的依赖库dsp_math。

为了充分利用硬件性能,您需要了解DSP特性并使用好这些特性(VLIW、SIMD)。在进行开发时,可参照Cadence本身已实现的基础运算。

vecmaxf SIMD实现如下:

/**
 * DSP find max value
 * @param[in] x: input
 * @param[in] N: length
 * @return maximum value
 */
float32_t vecmaxf(const float32_t *x, int32_t N) {
  const xb_vecN_2xf32 *restrict px;
  valign al_px;
  xb_vecN_2xf32 vecmax0, vecmax1, vecx0, vecx1;
  vboolN_2 b_max0, b_max1;
  int32_t n, N_tail, Nb_tail;
  float32_t max;

  // ASSERT( x );
  if (N <= 0) return 0.f;

  px = (const xb_vecN_2xf32 *)x;
  al_px = IVP_LAN_2XF32_PP(px);
  vecmax0 = vecmax1 = minus_inff_.f;

  /* Main loop: process by 2*IVP_N_2 values per iteration */
  for (n = 0; n<N>> (LOG2_IVP_N_2 + 1); n++) {
    IVP_LAN_2XF32_IP(vecx0, al_px, px);
    IVP_LAN_2XF32_IP(vecx1, al_px, px);
    vecmax0 = IVP_MAXN_2XF32(vecx0, vecmax0);
    vecmax1 = IVP_MAXN_2XF32(vecx1, vecmax1);
  }
  /* Process last N%(2*IVP_N_2) values */
  N_tail = N & (2 * IVP_N_2 - 1);
  Nb_tail = N_tail * sizeof(float32_t);
  b_max0 = IVP_LTRSN_2(N_tail);
  b_max1 = IVP_LTRSN_2(N_tail - IVP_N_2);
  IVP_LAVN_2XF32_XP(vecx0, al_px, px, Nb_tail);
  IVP_LAVN_2XF32_XP(vecx1, al_px, px, Nb_tail - IVP_N_2 * sizeof(float32_t));
  IVP_MAXN_2XF32T(vecmax0, vecx0, vecmax0, b_max0);
  IVP_MAXN_2XF32T(vecmax1, vecx1, vecmax1, b_max1);
  /* Reduce maximium values from vectors to the scalar one */
#ifdef IVP_SELI_32B_ROTATE_RIGHT_16
  vecmax0 = IVP_MAXN_2XF32(vecmax0, vecmax1);
  vecmax1 = IVP_SELN_2XF32I(vecmax0, vecmax0, IVP_SELI_32B_ROTATE_RIGHT_16);
#endif
  vecmax0 = IVP_MAXN_2XF32(vecmax0, vecmax1);
  vecmax1 = IVP_SELN_2XF32I(vecmax0, vecmax0, IVP_SELI_32B_ROTATE_RIGHT_8);
  vecmax0 = IVP_MAXN_2XF32(vecmax0, vecmax1);
  vecmax1 = IVP_SELN_2XF32I(vecmax0, vecmax0, IVP_SELI_32B_ROTATE_RIGHT_4);
  vecmax0 = IVP_MAXN_2XF32(vecmax0, vecmax1);
  vecmax1 = IVP_SELN_2XF32I(vecmax0, vecmax0, IVP_SELI_32B_ROTATE_RIGHT_2);
  vecmax0 = IVP_MAXN_2XF32(vecmax0, vecmax1);
  vecmax1 = IVP_SELN_2XF32I(vecmax0, vecmax0, IVP_SELI_32B_ROTATE_RIGHT_1);
  vecmax0 = IVP_MAXN_2XF32(vecmax0, vecmax1);
  max = IVP_MOVF32_FROMN_2XF32(vecmax0);

  return max;
} /* vecmaxf() */

vecexpf_max SIMD实现如下:

/**
 * DSP Vectorized Floating-Point Exponential
 * The exponential (or anti-logarithm) function computes the exponential
 * value e to the power of input vector x[N], and stores the result to output
 * vector z[N].
 * @param[out] z: output
 * @param[in] x: input
 * @param[in] N: length
 * @param[in] max_value: max_value
 * @return maximum value
 */
void vecexpf_max(float32_t *z, const float32_t *x, int32_t N,
                 float32_t max_value) {
  const xb_vecN_2xf32 *restrict px = (const xb_vecN_2xf32 *)x;
  xb_vecN_2xf32 *restrict pz = (xb_vecN_2xf32 *)z;
  xtfloat *restrict ptbl = (xtfloat *)expftblf;

  // check correct
  xb_vecN_2xf32 xmax = max_value;
  xb_vecN_2xf32 xin, xin2, txin, zout;
  xb_vecN_2xf32 p0, p1, p2, p3, p4, p5, p6;
  xb_vecN_2xf32 scl1, scl2;
  xb_vecN_2x32v t, exp_fract, exp_int, e1, e2;
  xb_vecN_2x64w W;
  xb_vecNx16 invln2;
  valign xa, za;
  vboolN_2 b_nan, b_max, b_inf;
#if EXPF_ERRH != 0
  vboolN_2 b_edom, b_erange;
  xb_int32v SCF; /* Floating-point Status and Control Register values. */
#endif
  int32_t n;

  /* common argument checks */
  // NASSERT(x);
  // NASSERT(z);
  if (N <= 0) return;

  /* load 1/ln(2) constant in Q30 */
  invln2 =
      IVP_MOVNX16_FROMN_2X32(IVP_LSN_2X32_I((const int32_t *)&invln2_Q30, 0));

  za = IVP_ZALIGN();
  xa = IVP_LAN_2XF32_PP(px);

  for (n = 0; n<(N + IVP_N_2 - 1)>> LOG2_IVP_N_2; n++) {
    IVP_LAVN_2XF32_XP(xin, xa, px, (uint8_t *)x + N * 4 - (uint8_t *)px);

    txin = xin;
    /* Check input for values that are out of domain/range */
    b_nan = IVP_UNN_2XF32(xin, xin);              /* x==NaN                */
    b_max = IVP_ULEN_2XF32(expfminmax[1].f, xin); /* x>=88.72284 or x==NaN */
    b_inf = IVP_UEQN_2XF32(xin, plus_inff.f);     /* x==+Inf or x==NaN     */

    /* Limit input values to [-128;+127] and replace NaNs *
     * with some numbers to avoid unnecessary exceptions  */
    xin = IVP_SUBN_2XF32(xin, xmax);
    xin = IVP_MOVN_2XF32T(127.0f, xin, b_max);
    xin = IVP_MAXN_2XF32(xin, -128.0f);
    /* Convert the input to Q24 and scale to 1/ln(2) */
    t = IVP_TRUNCN_2XF32(xin, 24);
    W = IVP_MULN_2X16X32_0(invln2, t);
    IVP_MULAHN_2X16X32_1(W, invln2, t); /* Q24*Q30->Q54 */

    /* Separate input to positive fractional part and integer part */
    exp_fract = IVP_PACKVRNRN_2X64W(W, 22);    /* Q54->Q32 */
    exp_fract = IVP_SRLIN_2X32U(exp_fract, 1); /* Q32->Q31 */
    exp_int = IVP_PACKHN_2X64W(W);             /* Q54->Q22 */
    exp_int = IVP_SRLIN_2X32(exp_int, 22);     /* Q22->Q0 */

    /* compute 2^fract in floating-point format */
    xin =
        IVP_FLOATN_2X32(exp_fract, 31); /* scale fraction part by 2^-31 with */
    /* conversion from int32_t to float32_t */

    /* Pass input to the output if x==+INF or x==NaN */
    xin = IVP_MOVN_2XF32T(txin, xin, b_inf);
    /* load polynomial coefficients */
    IVP_LSRN_2XF32_IP(p0, ptbl, sizeof(float32_t));
    IVP_LSRN_2XF32_IP(p1, ptbl, sizeof(float32_t));
    IVP_LSRN_2XF32_IP(p2, ptbl, sizeof(float32_t));
    IVP_LSRN_2XF32_IP(p3, ptbl, sizeof(float32_t));
    IVP_LSRN_2XF32_IP(p4, ptbl, sizeof(float32_t));
    IVP_LSRN_2XF32_IP(p5, ptbl, sizeof(float32_t));
    IVP_LSRN_2XF32_XP(p6, ptbl, -6 * (int32_t)sizeof(float32_t));
    /* compute polynomial using combination *
     * of Estrin`s and Horner schemes       */
    xin2 = IVP_MULN_2XF32(xin, xin);
    IVP_MULAN_2XF32(p1, xin, p0);
    IVP_MULAN_2XF32(p3, xin, p2);
    IVP_MULAN_2XF32(p5, xin, p4);

    IVP_MULAN_2XF32(p3, xin2, p1);
    IVP_MULAN_2XF32(p5, xin2, p3);

    IVP_MULAN_2XF32(p6, xin, p5);

    /* Apply integer exponential part to the result */
    exp_int = IVP_ADDN_2X32(exp_int, 254);
    e1 = IVP_SRLIN_2X32(exp_int, 1);
    e2 = IVP_SUBN_2X32(exp_int, e1);
    e1 = IVP_SLLIN_2X32(e1, 23);
    e2 = IVP_SLLIN_2X32(e2, 23);
    scl1 = IVP_MOVN_2XF32_FROMN_2X32(e1);
    scl2 = IVP_MOVN_2XF32_FROMN_2X32(e2);
    zout = IVP_MULN_2XF32(p6, scl1);
    zout = IVP_MULN_2XF32(zout, scl2);

    IVP_SAVN_2XF32_XP(zout, za, pz, (uint8_t *)z + N * 4 - (uint8_t *)pz);
  }
  IVP_SAPOSN_2XF32_FP(za, pz);
} /* vecexpf() */

vecsum SIMD实现如下:


/**
 * DSP Vectorized sum value
 * @param[in] x: input
 * @param[in] N: length
 * @return sum of x[N] value
 */
static float32_t vecsum(const float32_t *x, int32_t N) {
  const xb_vecN_2xf32 *restrict px;
  valign al_px;
  xb_vecN_2xf32 vecsum0, vecsum1, vecx0, vecx1;
  vboolN_2 b_sum0, b_sum1;
  int32_t n, N_tail, Nb_tail;
  float32_t sum;

  // ASSERT(x);
  if (N <= 0) return 0.f;
  px = (const xb_vecN_2xf32 *)x;
  al_px = IVP_LAN_2XF32_PP(px);
  vecsum0 = vecsum1 = 0.f;
  for (n = 0; n<N>> (LOG2_IVP_N_2 + 1); n++) {
    IVP_LAN_2XF32_IP(vecx0, al_px, px);
    IVP_LAN_2XF32_IP(vecx1, al_px, px);
    vecsum0 = IVP_ADDN_2XF32(vecx0, vecsum0);
    vecsum1 = IVP_ADDN_2XF32(vecx1, vecsum1);
  }
  N_tail = N & (2 * IVP_N_2 - 1);
  Nb_tail = N_tail * sizeof(float32_t);
  b_sum0 = IVP_LTRSN_2(N_tail);
  b_sum1 = IVP_LTRSN_2(N_tail - IVP_N_2);
  IVP_LAVN_2XF32_XP(vecx0, al_px, px, Nb_tail);
  IVP_LAVN_2XF32_XP(vecx1, al_px, px, Nb_tail - IVP_N_2 * sizeof(float32_t));
  IVP_ADDN_2XF32T(vecsum0, vecx0, vecsum0, b_sum0);
  IVP_ADDN_2XF32T(vecsum1, vecx1, vecsum1, b_sum1);

  vecsum0 = IVP_ADDN_2XF32(vecsum0, vecsum1);
  sum = IVP_RADDN_2XF32(vecsum0);
  return sum;
}

除法运算可变为乘法运算,同时实现乘法运算比较容易且性能较好。

/**
 * DSP Vectorized multiply
 * @param[out] z: output
 * @param[in] x1: multiply left input
 * @param[in] x2: multiply right input
 * @param[in] N: length
 */
static void vecmul(float32_t *z, const float32_t *x1, const float32_t x2,
                   int32_t N) {
  const xb_vecN_2xf32 *restrict px;
  xb_vecN_2xf32 *restrict pz;
  valign al_px, al_pz;

  px = (const xb_vecN_2xf32 *)x1;
  pz = (xb_vecN_2xf32 *)z;
  xb_vecN_2xf32 px2 = x2;

  al_px = IVP_LAN_2XF32_PP(px);
  al_pz = IVP_ZALIGN();

  int32_t n, modN;
  modN = (N & (IVP_N_2 - 1)) * sizeof(float32_t);
  xb_vecN_2xf32 in, out;
  for (n = 0; n<N>> LOG2_IVP_N_2; n++) {
    IVP_LAN_2XF32_IP(in, al_px, px);
    out = IVP_MULN_2XF32(in, px2);
    IVP_SAN_2XF32_IP(out, al_pz, pz);
  }

  IVP_LAN_2XF32_IP(in, al_px, px);
  out = IVP_MULN_2XF32(in, px2);
  IVP_SAVN_2XF32_XP(out, al_pz, pz, modN);
  IVP_SAPOSN_2XF32_FP(al_pz, pz);
}

完整的Softmax实现如下:

static int32_t dsp_softmax(float32_t *input, int32_t length,
                           float32_t *output) {
  float32_t max_value = vecmaxf(input, length);
  DSP_LOGD("max_value %f", max_value);
  vecexpf_max(output, input, length, max_value);
  float32_t sum = vecsum(output, length);
  DSP_LOGD("sum %f", sum);

  int32_t i = 0;
  float32_t div = 1 / sum;
  vecmul(output, output, div, length);
  return 0;
}

以上Softmax实现直接访问DDR,由于J6没有配置向量缓存,故直接访问DDR内存非常耗时。常见的优化实现是,将数据分成tile块,通过idma搬移到TCM(TCM与缓存延迟相当)中,直接访问TCM进行计算。并且,为了将计算与idma搬运操作并行,DSP配备了两块TCM,以实现ping pong DMA的操作,如下图所示。

pingpong-idma

ping pong DMA的实现参考代码如下:

/**
 * DSP tile Vectorized max
 * @param[out] output_addr: tcm output addr
 * @param[in] in_addr: tcm input addr
 * @param[in] size: tcm compute size
 * @param[out] max: max value addr: max value addr
 * @param[in] param: input param
 */
void tile_max(float32_t *out_addr, float32_t *in_addr, int32_t size,
              float32_t *max, float32_t param) {
  float32_t max_value = vecmaxf(in_addr, size);
  DSP_LOGD("max_value %f, size %d", max_value, size);
  *max = max_value > *max ? max_value : *max;
}

/**
 * DSP tile Vectorized sum
 * @param[out] output_addr: tcm output addr
 * @param[in] in_addr: tcm input addr
 * @param[in] size: tcm compute size
 * @param[out] sum: sum value addr: sum value addr
 * @param[in] param: input param
 */
void tile_sum(float32_t *out_addr, float32_t *in_addr, int32_t size,
              float32_t *sum, float32_t param) {
  vecexpf_max(out_addr, in_addr, size, param);
  *sum += vecsum(out_addr, size);
}

/**
 * DSP tile Vectorized mul
 * @param[out] output_addr: tcm output addr
 * @param[in] in_addr: tcm input addr
 * @param[in] size: tcm compute size
 * @param[out] out: out value addr: out value addr
 * @param[in] param: input param
 */
void tile_mul(float32_t *out_addr, float32_t *in_addr, int32_t size,
              float32_t *out, float32_t param) {
  float32_t div = 1.0f / param;
  vecmul(out_addr, in_addr, div, size);
}

/**
 * DSP softmax ping-pong framework
 * @param[out] dst: optional
 * @param[in] src
 * @param[in] length
 * @param[in] func: framework tile compute func
 * @param[out] func_out: func output param
 * @param[in] func_in: func input param
 * @param[in] with_out_idma: whether need to copy back output
 * @return 0 if success, return defined error code otherwise
 */
int32_t hb_dsp_ping_pong_frame(float32_t *dst, float32_t *src, void *src_idma[],
                               void *dst_idma[], int32_t length,
                               void (*func)(float32_t *, float32_t *, int32_t,
                                            float32_t *, float32_t),
                               float32_t *func_out, float32_t func_in,
                               bool with_out_idma = true) {
  int32_t size[] = {0, 0};
  int32_t vret_in[2];
  int32_t vret_out[2];
  int32_t i = 0;
  int32_t block_start = 0;
  int32_t output_start = 0;
  int32_t ping_pong = 0;

  int32_t block = XT_MIN(TILE_SIZE >> 2, length);
  int32_t block_size = block << 2;
  size[0] = block;
  float32_t sum = 0.f;
  IDMA_COPY(HB_IDMA_CH0, vret_in[0], src, src_idma[0], block_size)
  if (vret_in[0] < 0) {
    DSP_LOGE("copy task failed!");
  }

  block_start += block;
  if (block_start < length) {
    block = XT_MIN(block, length - block_start);
    block_size = block << 2;
    size[1] = block;

    IDMA_COPY(HB_IDMA_CH1, vret_in[1], src + block_start, src_idma[1],
              block_size)
    if (vret_in[1] < 0) {
      DSP_LOGE("copy task failed!");
    }
    block_start += block;
  }

  while (i < length) {
    i += size[ping_pong];
    IDMA_WAIT(ping_pong, vret_in[ping_pong])
    if (with_out_idma) {
      IDMA_WAIT(ping_pong, vret_out[ping_pong])
    }
    func((float32_t *)dst_idma[ping_pong], (float32_t *)src_idma[ping_pong],
         size[ping_pong], func_out, func_in);

    if (with_out_idma) {
      IDMA_COPY(ping_pong, vret_out[ping_pong], dst_idma[ping_pong],
                dst + output_start, size[ping_pong] << 2)
      if (vret_out[ping_pong] < 0) {
        DSP_LOGE("Fail to copy softmax output, status %d", vret_out[ping_pong]);
        return -1;
      }
      output_start += size[ping_pong];
    }

    if (block_start < length) {
      block = XT_MIN(block, length - block_start);
      block_size = block << 2;
      size[ping_pong] = block;

      IDMA_COPY(ping_pong, vret_in[ping_pong], src + block_start,
                src_idma[ping_pong], block_size)
      if (vret_in[ping_pong] < 0) {
        DSP_LOGE("copy task failed!");
      }
      block_start += block;
    }
    ping_pong ^= 0x1;
  }
  if (with_out_idma) {
    IDMA_WAIT(ping_pong ^ 0x01, vret_out[ping_pong ^ 0x01])
  }
  return 0;
}

完整的DSP softmax实现如下:

#define PROF
/*
 * hb_dsp_softmax have two implementation, ping-pong and direct call ddr
 * 1. ping-pong: use idma to transfer ddr data to tcm, then use tile compute(simd optimization)
 * 2. direct ddr: use simd(same with tile compute) to directly load and store ddr
 * 3. profiling: ping-pong use 5154293 cycles, direct ddr use 7648254 cycles on j6 in this scenario
 */
int32_t hb_dsp_softmax(void *input, void *output, void *tm) {
  DSP_LOGD("enter hb_dsp_softmax\n");
  hbDSPSoftmaxParam *ptr = (hbDSPSoftmaxParam *)(input);
  float32_t *src = (float32_t *)hb_dsp_mem_map(ptr->input, ptr->data_size);
  PRT_IF_COND_RETURN(MAP_FAILED == src, HB_ERR_MMAP_FAILED)
  float32_t *dst = (float32_t *)(output);

  // ping-pong optimization
  if (ptr->run_tcm_opt) {
    xvTileManager *tm_ = (xvTileManager *)tm;
    float32_t *src_0 = (float32_t *)(xvAllocateBuffer(
        tm_, TILE_SIZE, XV_MEM_BANK_COLOR_0, IVP_2N));
    float32_t *dst_0 = src_0;
    float32_t *src_1 = (float32_t *)(xvAllocateBuffer(
        tm_, TILE_SIZE, XV_MEM_BANK_COLOR_1, IVP_2N));
    float32_t *dst_1 = src_1;
    void *src_idma[] = {src_0, src_1};
    void *dst_idma[] = {dst_0, dst_1};
    int32_t length = ptr->data_size >> 2;
    DSP_LOGD("length is %d\n", length);

#ifdef PROF
    TIME_STAMP(cycles_start);
#endif
    // find max
    DSP_LOGD("find max\n");
    float32_t max_value = 0.f;
    hb_dsp_ping_pong_frame(dst, src, src_idma, dst_idma, length, tile_max,
                           &max_value, 0, false);
    DSP_LOGD("max_value %f", max_value);

    // exp & sum
    DSP_LOGD("exp & sum");
    float32_t sum_value = 0.f;
    hb_dsp_ping_pong_frame(dst, src, src_idma, dst_idma, length, tile_sum,
                           &sum_value, max_value, true);
    DSP_LOGD("sum_value %f", sum_value);

    // mul
    DSP_LOGD("mul");
    hb_dsp_ping_pong_frame(dst, dst, src_idma, dst_idma, length, tile_mul, 0,
                           sum_value, true);

    xvFreeBuffer(tm_, (void *)src_0);
    xvFreeBuffer(tm_, (void *)src_1);

#ifdef PROF
    TIME_STAMP(cycles_stop);
    self_cycles = cycles_stop - cycles_start;
    DSP_LOGI("find softmax ping-pong tcm is %d cycles", self_cycles);
#endif
  } else {
    // direct call ddr, because use simd, so not need flush cache
#ifdef PROF
    TIME_STAMP(cycles_start);
#endif
    int32_t N = ptr->data_size >> 2;
    int32_t ret = dsp_softmax(src, N, dst);
    if (ret != 0) {
      DSP_LOGE("Run softmax op fail:%d", ret);
      return ret;
    }

#ifdef PROF
    TIME_STAMP(cycles_stop);
    self_cycles = cycles_stop - cycles_start;
    DSP_LOGI("find softmax ddr tcm is %d cycles", self_cycles);
#endif  // PROF
  }
  hb_dsp_mem_unmap((uint32_t)src);

  return 0;
}

注册运行

算子开发完成后,通过调用hb_dsp_register_fn接口注册hb_dsp_softmax算子,完成注册后,编译DSP镜像。

typedef int (*handle_fn)(void *input, void *output, void *tm);

/**
 * register custom op, should be called before hb_dsp_start
 * @param[in] latency: op latency(microseconds), if no latency info, set 0
 * @return 0 if success, return defined error code otherwise
 */
int hb_dsp_register_fn(int cmd, handle_fn handle, int latency);
注解

如果是x86仿真运行,则无需启动镜像,如果是上板运行,需要执行脚本dsp_deploy.sh启动镜像。