写点什么

飞桨 x 昇腾生态适配方案:06_ 算子适配举例

作者:小顺637
  • 2025-05-12
    北京
  • 本文字数:2299 字

    阅读完需:约 8 分钟

本节介绍 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_castphi::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 的转置内核函数,需在算子开发代码中进行函数声明,如图所示:



用户头像

小顺637

关注

还未添加个人签名 2023-01-19 加入

还未添加个人简介

评论

发布
暂无评论
飞桨x昇腾生态适配方案:06_算子适配举例_飞桨_小顺637_InfoQ写作社区