---
title: Compute函数实现
description: "Compute函数实现示例如下，主要包括合法性校验和计算逻辑实现两部分，可根据需要在合适的地方调用Dump日志接口，打印相关调试信息。示例中使用到的API接口介绍可参见AI CPU API。"
url: https://www.hiascend.com/document/detail/zh/canncommercial/latest/others/tbeaicpudevg/atlasopdev_10_0076.html
sourcePath: /source/zh/canncommercial/900/others/tbeaicpudevg/atlasopdev_10_0076.html
indexId: 830db97b1618129673baa5883d3751a7f62108fee2c7634100c3feba48bc0b8273
---
# Compute函数实现

Compute函数实现示例如下，主要包括合法性校验和计算逻辑实现两部分，可根据需要在合适的地方调用Dump日志接口，打印相关调试信息。示例中使用到的API接口介绍可参见AI CPU API。

```
// 从context中获取input tensor
Tensor *input = ctx.Input(0);
// 对输入tensor进行基本校验
// 例如，对获取到的input进行空指针校验
if (input == nullptr) {
return 1;
}
// 获取input tensor的shape信息
auto inputShape = input->GetTensorShape();
for (int32_t i = 0; i < inputShape->GetDims(); ++i) {
// 根据需要调用Dump日志接口，打印相关调试信息
CUST_KERNEL_LOG_DEBUG(ctx, "dim[%d] size:%ld.", i, inputShape->GetDimSize(i));
}
// 获取input tensor的DataType
DataType inputType = input->GetDataType();
// 获取input tensor的数据地址
auto inputData = input->GetData();
// 获取输出tensor的数据地址以及shape
Tensor *output = ctx.Output(0);
auto outputShape = output->GetTensorShape();
auto outputData = output->GetData();
// 保存输出结果
outputData[0] = inputData[0];
```

#### 合法性校验

在计算逻辑实现之前需要进行合法性校验，一般包含如下几类：- 对获取到的Input进行空指针校验，此校验必选。
- 对输入输出个数进行校验。
- 对算子输入的内在逻辑进行校验。
  例如，对于多输入算子，多个tensor的dtype需要保持一致，此时需要校验多个输入的dtype是否一致。若多输入的内在逻辑要求已经在算子原型定义的Verify函数中进行实现，则compute函数中的此校验可不再实现。

- 对dtype的校验。
  可根据算子实际情况来选择是否进行dtype的校验，若某算子仅支持A、B两种数据类型，其他数据类型都不支持，此时可在实现算子计算逻辑前对dtype进行校验，判断dtype是否在支持的dtype列表中。


#### 计算逻辑实现

完成入参校验后，根据算子输入支持的数据类型分别进行计算，伪代码如下，其中OpCompute函数为算子计算过程实现函数。
```
// 获取第i个输入的类型
auto data_type = ctx.Input(i)->GetDataType();
switch (data_type) {
case DT_FLOAT16:
return OpCompute<Eigen::half>(...);
case DT_FLOAT:
return OpCompute<float>(...);
case DT_DOUBLE:
return OpCompute<double>(...);
case DT_INT8:
return OpCompute<int8_t>(...);
case DT_INT16:
return OpCompute<int16_t>(...);
... ...
default:
return PARAM_INVAILD;
}
```


算子计算逻辑实现时，有以下几个注意点：

- 由于C++自身不支持半精度浮点类型，对于半精度数据类型的计算实现，可使用第三方库Eigen来表示（建议使用3.3.9版本），具体可参考资料：  LINK(https://gitlab.com/libeigen/eigen)
。
  例如，对于Less算子，输入为半精度时，用Eigen进行强转。

```
auto input = reinterpret_cast<Eigen::half *>(input_0->GetData());
```


  说明：

  第三方Eigen库还提供了比较系统的矩阵和向量等线性代数相关的运算操作，若您的算子实现涉及到相关操作，可以借助Eigen库实现。

  例如，使用Eigen库进行矩阵的定义、初始化，并求取行列式的代码示例如下所示：

```
#include "Eigen/Dense"
int m,n;
Eigen::Matrix<float, Eigen::Dynamic, Eigen::Dynamic> eMatrix(m, n);
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
eMatrix(i, j) = i * m + j * n;
}
}
//Using eigen to calculate the Determinant
float result = eMatrix.determinant();
```


- 对于动态shape算子，无法根据  算子原型定义
的InferShape推导得到输出tensor的shape，所以需要在Compute函数中完成输出shape的计算与更新。

```
std::vector<int64_t> dims = {inputData[0], inputData[1], 3, 4}
outputShape ->SetDimSizes(dims);
```


  Sample仓中的UniqueCust(https://gitee.com/ascend/samples/blob/master/cplusplus/level1_single_api/4_op_dev/1_custom_op/cpukernel/impl/unique_cust_kernels.cc)算子为动态Shape算子，您可以参考UniqueCust算子的代码实现。

- 算子计算过程可分块并行执行，这样可以有效地使用AI处理器的硬件资源，使性能达到最优。
- 分块并行为性能提升的一个手段，开发者可根据对算子的性能要求选择是否需要进行并行计算。
- 分块并行执行的功能，仅支持输入参数间独立运算的场景，若输入参数间存在数据依赖，则无法进行分块并行计算。
- 使用此功能时，需要在  算子信息库定义
中设置“opInfo.flagSupportBlockDim”为“True”，并设置“opInfo.functionName”为“RunCpuKernelWithBlock”。


  1. 首先使用    GetAttr
接口获取分块数目以及本次计算的分块ID，示例如下：

```
uint32_t blockdim = ctx.GetAttr("block_num")->GetInt();
uint32_t blockid = ctx.GetAttr("block_id")->GetInt();
```


    说明：分块数目是系统根据用户配置的BlockDim切分原则（算子信息库中配置的opInfo.blockDimByIndex）及CPU核数自动计算的。每一个分块都会分配一个“block_id”，“block_id”的取值范围为blockdim-1。

    获取“block_num”及“block_id”，用户可自行进行一些基本校验。

  2. 计算本次计算（当前分块的计算）的偏移量及数据量。
    例如，若“opInfo.blockDimByIndex”配置为-1，即按照第一个输入参数的元素个数进行BlockDim的切分，则计算偏移量及数据量的代码示例如下：

```
// 获取第一个输入参数的元素个数
int64_t total = input0->NumElements();
int64_t startpos = 0;
int64_t len = total;
if (blockdim != 1) {
// 计算每一块的最大数据量
uint32_t per_unit = std::ceil(total / blockdim);
// 得出本次计算的偏移量
startpos =  blockid * per_unit;
// 得出本次计算的数据量。
// blockid的取值范围为：0
~
blockdim-1，为避免最后一块数据存在拖尾，所以当blockid为最后一块时，len取值为total - per_unit * (blockdim - 1)
len = blockid < blockdim - 1 ? per_unit : (total - per_unit * (blockdim - 1));
}
```


  3. 进行算子的计算逻辑的实现。
    以Add算子为例，代码示例如下所示：

```
for (int i = startpos; i < startpos + len; i++) {
y[i] = x0[i] + x1[i];
}
```


完整的分块并行计算的算子样例可参见Ascend Sample仓(https://gitee.com/ascend/samples/blob/7e63a5116b207e98328eb1b562c021805bb21af9/cplusplus/level1_single_api/4_op_dev/1_custom_op/cpukernel/impl/add_block_cust_kernels.cc)。

#### 日志Dump功能使用

日志Dump功能用于记录AI CPU算子执行过程中的日志信息，方便进行AI CPU算子的功能调测。使用步骤如下：

1. 根据需要在Compute函数中调用日志dump接口输出相应级别的日志信息，接口的具体介绍请参见  Dump日志接口
。代码示例如下：

```
uint32_t UniqueCpuKernel::Compute(CpuKernelContext &ctx) {
Tensor *param_tensor = ctx.Input(0);
if (param_tensor == nullptr) {
return 1;
}
auto param_shape = param_tensor->GetTensorShape();
if (param_shape == nullptr) {
return 1;
}
int64_t p_size = 1;
for (int i = 0; i < param_shape->GetDims(); ++i) {
p_size *= param_shape->GetDimSize(i);
}
CUST_KERNEL_LOG_DEBUG(ctx, "Cust UniqueCpuKernel Compute, p_size is %ld.", p_size);
}
```


2. (可选)通过AI CPU 算子信息库中opInfo.workspaceSize参数配置用于记录AICPU算子日志信息的内存大小。默认值为2KB。具体参数说明参见  AI CPU算子信息库
。
3. 算子运行之前开启Dump功能，使得日志Dump功能生效。如何开启Dump功能依赖于具体的网络运行方式。以TensorFlow在线推理为例，sess.run模式下，通过session配置enable_dump、dump_path、dump_mode配置Dump参数，示例如下：

```
import tensorflow as tf
import numpy as np
sess_config = tf.compat.v1.ConfigProto()
from npu_bridge.estimator import npu_ops
custom_op = sess_config.graph_options.rewrite_options.custom_optimizers.add()
custom_op.name = "NpuOptimizer"
custom_op.parameter_map["enable_data_pre_proc"].b = True
custom_op.parameter_map["use_off_line"].b = True
custom_op.parameter_map["min_group_size"].b = 1
# enable_dump:是否开启dump功能
custom_op.parameter_map["enable_dump"].b = True
# dump_path:dump数据存放路径
custom_op.parameter_map["dump_path"].s = tf.compat.as_bytes("/test/log")
# dump模式配置为all时，可以Dump AI CPU日志信息
custom_op.parameter_map["dump_mode"].s = tf.compat.as_bytes("all")
tensor = tf.constant([1,2,3,4,5,6,7,8,9], dtype=tf.float32)
output, idx = tf.raw_ops.Unique(x=tensor)
with tf.compat.v1.Session(config=sess_config) as sess:
print("data = ", sess.run([output,idx]))
print("end proc")
```


  算子运行完成后，在Dump数据存放路径下会有日志Dump文件生成，文件名命名规则格式为{op_type}.{op_name}.{taskid}.{stream_id}.{timestamp}，其中{op_type}表示算子类型，{op_name}表示算子名称，{taskid}表示调用算子计算接口的taskId，{stream_id}表示算子具体执行的流Id，{timestamp}表示时间戳。

4. 使用开发套件包中的Dump文件解析工具解析日志Dump文件。开发套件包安装请参考。安装完成后，执行{install_path}/tools/operator_cmp/compare路径下的dump_parser.py脚本（{install_path}为套件包安装目录），即可实现对日志Dump文件的解析。
  命令示例和参数说明如下：

```
python3 dump_parser.py save_log -d dump_file [-out output]
```


**表1 参数说明表**

| 参数名 | 描述 | 是否必选 |
| --- | --- | --- |
| \-d \-\-dump\_file | 待解析的日志Dump文件。 | 是 |
| \-out \-\-output | 解析后落盘日志的存放目录，默认为当前路径。 | 否 |


  解析后落盘日志的命名规则为{dump_file_name}.{index}.log，其中{dump_file_name}为日志Dump文件的名称，{index}为日志文件索引号。例如， 执行如下命令，会在当前目录生成文件名为Unique.Unique.3.2.1671845532156774.0.log的日志文件。

```
python3 dump_parser.py save_log -d Unique.Unique.3.2.1671845532156774
```


网络运行时，使能exception_dump开关，AICPU算子执行异常时，会生成异常算子的日志Dump信息，解析方法同步骤4。
