diff --git "a/Z25070017 \346\256\265\345\255\246\351\276\231/.keep" "b/Z25070017 \346\256\265\345\255\246\351\276\231/.keep" new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git "a/Z25070017 \346\256\265\345\255\246\351\276\231/\344\273\243\347\240\201" "b/Z25070017 \346\256\265\345\255\246\351\276\231/\344\273\243\347\240\201" new file mode 100644 index 0000000000000000000000000000000000000000..09450f9fda7f1151117e8b940382d081307143aa --- /dev/null +++ "b/Z25070017 \346\256\265\345\255\246\351\276\231/\344\273\243\347\240\201" @@ -0,0 +1,160 @@ +一、AscendC Kernel 定义与初始化 +// sobel_custom_kernel.h / sobel_custom.cpp(合并展示) + +#include "kernel_operator.h" + +using namespace AscendC; + +class SobelCustomKernel { +public: + __aicore__ inline SobelCustomKernel() {} + + __aicore__ inline void Init( + GM_ADDR input, + GM_ADDR output, + uint32_t height, + uint32_t width, + uint32_t channels + ) { + // 保存 GM 地址 + input_gm = input; + output_gm = output; + + // 保存图像参数 + img_h = height; + img_w = width; + img_c = channels; + + // 初始化队列 + input_queue.Init(); + output_queue.Init(); + + // 分配 Unified Buffer 中的中间缓冲区 + gray_buf.Init(img_h * img_w); + gx_buf.Init(img_h * img_w); + gy_buf.Init(img_h * img_w); + edge_buf.Init(img_h * img_w); + } + + __aicore__ inline void Process(); + +private: + // Global Memory 地址 + GM_ADDR input_gm; + GM_ADDR output_gm; + + // 图像尺寸参数 + uint32_t img_h; + uint32_t img_w; + uint32_t img_c; + + // 输入 / 输出队列 + TQue input_queue; + TQue output_queue; + + // 中间计算缓冲区 + TBuf gray_buf; + TBuf gx_buf; + TBuf gy_buf; + TBuf edge_buf; +}; + +二、完整算子计算流程(CopyIn–Compute–CopyOut) +__aicore__ inline void SobelCustomKernel::Process() { + + /* ======================= + * 1. CopyIn:数据搬入 + * ======================= */ + LocalTensor input_local = + input_queue.AllocTensor(); + + DataCopy(input_local, + input_gm, + img_h * img_w * img_c); + + input_queue.EnQue(input_local); + + input_local = input_queue.DeQue(); + + + /* ======================= + * 2. RGB → Gray + * ======================= */ + LocalTensor gray = gray_buf.Get(); + + for (uint32_t i = 0; i < img_h * img_w; ++i) { + uint8_t r = input_local[i * 3 + 0]; + uint8_t g = input_local[i * 3 + 1]; + uint8_t b = input_local[i * 3 + 2]; + + // 简化的灰度转换 + gray[i] = Cast( + r * 0.299f + g * 0.587f + b * 0.114f + ); + } + + + /* ======================= + * 3. Sobel X / Y 计算 + * ======================= */ + LocalTensor gx = gx_buf.Get(); + LocalTensor gy = gy_buf.Get(); + + for (uint32_t y = 1; y < img_h - 1; ++y) { + for (uint32_t x = 1; x < img_w - 1; ++x) { + + uint32_t idx = y * img_w + x; + + half tl = gray[(y - 1) * img_w + (x - 1)]; + half tc = gray[(y - 1) * img_w + x]; + half tr = gray[(y - 1) * img_w + (x + 1)]; + + half ml = gray[y * img_w + (x - 1)]; + half mr = gray[y * img_w + (x + 1)]; + + half bl = gray[(y + 1) * img_w + (x - 1)]; + half bc = gray[(y + 1) * img_w + x]; + half br = gray[(y + 1) * img_w + (x + 1)]; + + gx[idx] = -tl + tr - 2 * ml + 2 * mr - bl + br; + gy[idx] = -tl - 2 * tc - tr + bl + 2 * bc + br; + } + } + + + /* ======================= + * 4. 边缘融合与量化 + * ======================= */ + LocalTensor edge = edge_buf.Get(); + + for (uint32_t i = 0; i < img_h * img_w; ++i) { + half mag = Abs(gx[i]) + Abs(gy[i]); + + if (mag > 255.0f) { + mag = 255.0f; + } + + edge[i] = Cast(mag); + } + + + /* ======================= + * 5. CopyOut:结果写回 + * ======================= */ + LocalTensor output_local = + output_queue.AllocTensor(); + + DataCopy(output_local, + edge, + img_h * img_w); + + output_queue.EnQue(output_local); + + output_local = output_queue.DeQue(); + + DataCopy(output_gm, + output_local, + img_h * img_w); + + output_queue.FreeTensor(output_local); +} diff --git "a/Z25070017 \346\256\265\345\255\246\351\276\231/\345\256\236\351\252\214\346\212\245\345\221\212" "b/Z25070017 \346\256\265\345\255\246\351\276\231/\345\256\236\351\252\214\346\212\245\345\221\212" new file mode 100644 index 0000000000000000000000000000000000000000..8989ac7271f31287a72fbd7b4ee1f1cffae6dbe1 --- /dev/null +++ "b/Z25070017 \346\256\265\345\255\246\351\276\231/\345\256\236\351\252\214\346\212\245\345\221\212" @@ -0,0 +1,78 @@ +AscendC 自定义算子开发实验报告——图像边缘检测算子设计与实现 +一、实验背景与实验目的 +1.实验背景 + 随着人工智能和计算机视觉技术的发展,越来越多的应用场景开始依赖高性能加速硬件来完成计算任务。昇腾(Ascend)NPU 作为面向AI场景设计的专用处理器,能够在图像处理、深度学习推理等任务中提供更高的并行计算能力。 + 为了方便开发者针对特定算法进行优化,CANN 提供了AscendC自定义算子开发能力,使开发者可以在算子层面对计算过程和内存访问进行精细控制。通过自定义算子,可以将部分关键计算从CPU转移到NPU上执行,从而提升整体性能。 + 图像边缘检测是计算机视觉中的基础任务,在医学影像分析、工业检测、视频处理等场景中具有广泛应用。Sobel算子结构简单、实现清晰,非常适合作为AscendC自定义算子开发的实验示例。 +2.实验目的 +本实验的主要目标包括: +(1)熟悉 AscendC自定义算子的开发流程和工程结构; +(2)理解AscendC编程模型及其对NPU计算资源的抽象方式; +(3)掌握图像边缘检测算法在AscendC算子中的实现思路; +(4)学习算子Tiling设计、片上内存管理以及向量化计算方式; +(5)在不依赖鸿蒙设备实际运行的情况下,完成算子设计与理论分析。 + +二、实验环境 +1. 主机与开发方式 + 主机操作系统:Windows 10 / Windows 11 + 开发方式:Windows主机用于代码编写与工程管理,通过 Linux 子系统(WSL2 Ubuntu 22.04)或虚拟机使用 AscendC 工具链 + 说明:AscendC 工具链仅支持 Linux 环境,因此实验采用 Windows + Linux 子系统的方式完成算子开发与分析。 +2. Linux 开发环境 + 操作系统:Ubuntu 22.04(x86_64) + Python 版本:3.7–3.10 + 编译工具:gcc / g++ 7.0、cmake 3.22.1 +3. AscendC 与 CANN 工具链 + CANN DDK Tools:5.1.1.0 + AscendC 开发工具:tools_ascendc + 调试方式:ascendebug(用于流程验证与理论分析) + +三、实验原理说明 +1. 图像边缘检测原理 + 图像边缘通常出现在图像亮度变化较为明显的位置。边缘检测的核心思想就是通过对图像局部区域进行计算,找出这些变化剧烈的区域。Sobel 算子是一种经典的边缘检测方法,其基本思路是分别在水平方向和垂直方向上对图像进行滤波,从而得到两个方向上的变化信息。通过综合这两个方向的结果,可以判断图像中哪些位置存在明显边缘。在实际工程实现中,为了降低计算复杂度,通常不采用复杂的平方和开方运算,而是通过对两个方向结果进行绝对值求和来近似表示边缘强度。 +2. AscendC编程模型说明 + AscendC是面向昇腾NPU的自定义算子编程语言,其核心特点是通过抽象的方式管理计算和内存资源。AscendC将不同层级的存储空间抽象为不同的逻辑位置,例如: + GM:表示全局内存,通常用于存放输入和输出数据; + VECIN / VECCALC / VECOUT:表示片上 Unified Buffer,用于高效计算和中间结果存储。 +通过这种抽象方式,开发者无需关心具体硬件细节,即可完成高性能算子设计。 + +四、算子总体设计方案 +1. 算子整体处理流程 + 本实验中图像边缘检测算子的整体流程如下: + (1)从全局内存中读取输入 RGB 图像数据; + (2)将输入数据搬运到片上内存中; + (3)对RGB 图像进行灰度化处理; + (4)分别计算图像在水平方向和垂直方向上的边缘信息; + (5)对两个方向的结果进行融合,得到最终边缘强度; + (6)对结果进行归一化处理并转换为 8 位图像格式; + (7)将最终结果写回全局内存。 +2. 内存管理策略 + 由于 NPU 片上内存空间有限,算子实现中需要合理规划内存使用方式。本实验中采用的内存管理策略如下:输入数据通过队列方式从全局内存搬运至片上内存;中间计算结果存放在临时缓冲区中;输出数据在计算完成后统一写回全局内存;在实现过程中,通过对缓冲区进行分块和复用,尽量减少不必要的内存占用。 +3. Tiling 策略设计 + 为了在有限的片上内存中处理大尺寸图像,实验中采用 Tiling 策略对输入图像进行分块处理。 + Tiling 的基本思路是将整张图像划分为多个小块,每次只处理其中一块数据。这样既可以满足内存限制,又能够提高数据复用率。 + 在本实验中,图像块的宽度按照硬件对齐要求进行设置,高度满足边缘计算所需的最小条件。同时,引入双缓冲机制,使数据搬运与计算过程能够交替进行,从而提高整体执行效率。 + +五、算子实现方式 +AscendC 算子实现遵循推荐的三阶段执行模式: +1. 数据搬入阶段(CopyIn) + 在该阶段,将输入图像数据从全局内存搬运到片上内存中。对于图像边界或非对齐区域,使用数据填充方式保证后续计算的正确性。 +2. 计算阶段(Compute) + (1)在计算阶段,算子在片上内存中完成以下操作: + (2)图像灰度化处理; + (3)水平方向和垂直方向的边缘计算; + (4)两个方向结果的融合; + (5)输出数据格式转换。 +3. 数据搬出阶段(CopyOut) + 计算完成后,将结果从片上内存写回全局内存,供后续处理或应用程序使用。 + +六、工程结构说明 +实验工程采用 AscendC 官方推荐的算子工程结构,主要包括以下模块: + op_host:负责算子注册、参数检查以及 Tiling 策略配置; + op_kernel:负责算子的核心计算逻辑实现。 + +七、实验总结与心得体会 + 通过本次实验,对 AscendC 自定义算子开发流程有了系统认识,重点理解了以下内容: + (1)AscendC 算子的工程组织方式与开发流程; + (2)向量化编程范式在提升算子性能方面的作用; + (3)AscendC 算子在端侧和云侧场景中的良好复用能力。 + 本实验为后续深入学习异构计算和高性能算子优化提供了实践基础。 \ No newline at end of file