本节介绍aclnn算子的三种适配场景。

Paddle-API 与 CANN-Kernel 差异剖析及适配策略

对于Paddle-API与CANN-Kernel两者中常见的差别与适配方法如下:

Paddle参数缺失或者参数无法直接对应

  • 如果Paddle算子只需要CANN提供的某个参数为默认值的功能,则可通过默认赋值的方式完成
  • 考虑通过计算取得需要参数

    CANN参数缺失

  • CANN算子没有某个Paddle有的参数,一般是此算子CANN支持的模式少于Paddle
  • 可通过多个算子分别完成算子的部分功能(如max_pool + avg_pool)
  • 如果CANN只能支持部分功能,则可以在调用处抛出参数值判断异常

    数据类型不支持

  • 输入数据类型不匹配时需要在计算前插入 Cast 操作,并且要更改输出的数据类型,在计算后对输出数据进行 Cast 操作返回原数据类型

    layout转换

  • NPU算子基本不支持NHWC,但是部分Paddle算子支持,如果遇到这样的情况需要在计算前后插入 Transpose

    小算子拼接

  • 部分Paddle-API的功能在NPU中没法直接完成,但可通过多个小算子拼接完成,一般会少许影响性能

    加入缺少的参数

    以ReluGrad算子为例,通过计算或者默认赋值方式加入缺少的参数:

    在进行参数对齐时,需要检查是否存在需要默认参数的情况。以 Paddle 的 relugrad 算子为例,其对应的 aclnn 的 ThresholdBackward 算子包含额外参数 threshold。
    在实际操作中,可通过默认赋值的方式实现参数对齐,如图所示,代码为phi::Scalar threshold = 0.0。完成参数对齐后,即可直接调用 NPU 的 aclnnThresholdBackward 算子 。

    数据类型转换

    以 nll_loss 算子为例,Paddle API 与 CANN API 所支持的数据类型存在差异。Paddle API 中,输入 x 的数据类型为 double,而 CANN 的对应算子仅支持 float32 这一特定数据类型,具体情况如下图所示:
    Paddle侧:

    CAAN侧:

此情形需进行数据类型转换:

  • 首先,对输入数据执行 cast 操作,将其转换为 CANN 算子支持的数据类型;
  • 完成转换后,执行 NPU 的 aclnn 算子;
  • 算子运算结束后,再将计算结果的数据类型由 float32 转换回输入 x 原本的数据类型。
    具体流程如下图所示:

    数据类型转换需要Cast_kernel算子,下面为Cast_kernel算子的声明:

以下介绍将变量 x 的数据类型从 double 转换为 float32(转换后的变量记为 x_cast)的流程:

对象声明

phi::DenseTensor x_cast;                // 声明目标张量 x_cast
phi::DenseTensorMeta x_cast_meta;       // 声明张量的元数据对象

phi::DenseTensor:深度学习框架中表示多维数组的核心数据结构,包含数据和元信息(如形状、数据类型)。
phi::DenseTensorMeta:用于存储张量的元信息(metadata)。

元数据初始化

x_cast_meta = {phi::DataType::FLOAT32, x.dims();

phi::DataType::FLOAT32:明确将目标张量的数据类型设为 float32。
x.dims():继承输入张量 x 的维度信息(如 [batch_size, channels, height, width])

绑定元数据

x_cast.set_meta(x_cast_meta);  

作用:将初始化后的元数据绑定到目标张量 x_cast

执行类型转换

custom_kernel::CastKernel<T, Context>(dev_ctx, x, phi::DataType::FLOAT32, &x_cast);

核心参数:
dev_ctx: 设备上下文(如 CPU/GPU 资源管理)
x: 输入张量
phi::DataType::FLOAT32: 目标数据类型
&x_cast: 输出的目标张量指针
功能:将输入张量 x 的数据类型转换为 float32,结果写入 x_cast

执行NPU aclnn算子计算

把所有需要进行数据类型转换的参数转换完成后,使用算子执行宏 EXEC_NPU_CMD执行aclnn算子:

aclnn算子输出结果原类型恢复

  • 将 out_cast(NPU计算结果)转换为paddle api的 out 张量类型(如 float32 → double)。
  • 将损失计算中累计的权重值 total_weight_cast 数据类型转换为目标类型后写入 total_weight

    转置操作

    在 Pool2dGradKernel 算子中,若输入数据格式data_format为NHWC,即高度、宽度、通道数位于最后,鉴于 NPU 的操作要求,需将数据转换为NCHW格式。此转换通过Transpose操作达成。
    Transpose操作的核心功能是实现张量维度重排,旨在适配 NPU 计算特性所规定的数据布局需求。在本场景中,其主要作用是完成从NHWC(Channel Last)到NCHW(Channel First)这两种内存布局的转换 。

    流程图如下:

    变量声明

    phi::DenseTensor transformed_out_grad;
    通过临时变量隔离布局转换过程,保证原始数据的完整性

    布局判断逻辑

    接下来,程序执行条件判断if (channel_last)。该判断旨在检查输入数据是否采用NHWC格式。
    if (channel_last)条件成立,即NHWC(Channel Last)格式时,程序将执行转置操作,把数据格式转换为通道优先Channel First的NCHW格式。

维度置换规则

std::vector<int> perm = {0, 3, 1, 2};
数学原理:对应张量维度[N,H,W,C]->[N,C,H,W]
定义了一个perm向量{0, 3, 1, 2},这应该是用来重新排列维度的顺序。原来的维度假设是NHWC(0,1,2,3),转置后变为NCHW(0,3,1,2)。

新形状构建

接着构造了out_grad_tensor_shape,调整形状以匹配新的维度顺序。调整后的形状是通过重新排列out_grad的维度得到的,例如将原维度[0]、[3]、[1]、[2]组合成新的形状。

std::vector<int> out_grad_tensor_shape = {
    out_grad.dims()[0],
    out_grad.dims()[3], 
    out_grad.dims()[1],
    out_grad.dims()[2],
};

通过维度复制而非引用保证形状独立性。

内存分配

然后将transformed_out_grad调整大小,分配内存,并通过TransposeKernel进行转置操作。

transformed_out_grad.Resize(phi::make_ddim(out_grad_tensor_shape));
dev_ctx.template Alloc<T>(&transformed_out_grad);

转置运算

custom_kernel::TransposeKernel<T, Context>(dev_ctx, out_grad, perm, &transformed_out_grad);
custom_kernel::TransposeKernel是调用NPU的转置内核函数,需在算子开发代码中进行函数声明,如图所示:


讲道义的遥控器
1 声望0 粉丝