Skip to content

Latest commit

 

History

History
207 lines (164 loc) · 12.7 KB

File metadata and controls

207 lines (164 loc) · 12.7 KB

MaxPool2d 初学者教程

本目录围绕 MaxPool2d 算子展示了两个学习阶段的实现、一个运行脚本和如何快速上手的说明。所有示例都在 Experiments/04_maxpooling 下,可以直接参考对应文件。

1. 最小实现(maxpooling_simple.mlu

  • 使用 __bang_maxpool API 直接实现 MaxPool2d,这是最简单的方式;
  • API 优势__bang_maxpool 是 BangC 提供的内置函数,可以直接对整个 NCHW 格式的张量进行池化操作,无需手动处理窗口和循环;
  • 数据规模:使用较小的数据规模(2 batch × 4 channels × 64×64 特征图),便于理解 API 的基本用法;
  • 无手动窗口处理:不需要手动收集窗口元素,__bang_maxpool 会自动处理所有池化操作;
  • 适合初学者快速理解 __bang_maxpool API 的使用方式和参数含义。

API 参考文档Cambricon BANG C - __bang_maxpool

探索任务

  • API 理解 - __bang_maxpool 的参数

    • __bang_maxpool(dst, src, channel, height, width, kernel_height, kernel_width)
    • 实验:尝试修改各个参数(batch、channels、in_h、in_w、kernel_h、kernel_w),观察输出尺寸如何变化
    • 实验:理解 7 参数版本和 13 参数版本的区别:
      • 7 参数版本:__bang_maxpool(dst, src, channel, height, width, kernel_height, kernel_width),默认 stride = kernel_size
      • 13 参数版本:__bang_maxpool(dst, src, channel, height, width, kernel_height, kernel_width, padding_height, padding_width, stride_height, stride_width)
    • 思考:为什么 7 参数版本更简单?什么场景下需要使用 13 参数版本?
  • 数据布局理解 - NCHW 格式

    • __bang_maxpool 要求输入和输出都是 NCHW 格式(batch, channel, height, width)
    • 实验:观察代码中如何计算 TOTAL_INTOTAL_OUT,理解 NCHW 布局的内存排列
    • 实验:尝试修改 BATCHCHANNELS,观察内存使用如何变化
    • 思考:为什么使用 NCHW 格式而不是 NHWC?不同布局对性能有什么影响?
  • 输出尺寸计算

    • 输出尺寸公式(7 参数版本,stride = kernel_size):out_h = floor((in_h - kernel_h) / kernel_h) + 1
    • 实验:尝试不同的 kernel_hkernel_w 组合,验证输出尺寸公式
    • 实验:观察当 stride = kernel 时,输出尺寸如何变化(这是最常见的配置)
    • 思考:为什么输出尺寸公式是这样的?padding 如何影响输出尺寸?
  • NRAM 使用分析

    • 代码中使用了 nram_src[TOTAL_IN]nram_dst[TOTAL_OUT] 存储整个张量
    • 实验:尝试逐步增大输入尺寸(64×64 → 128×128 → 256×256...),观察何时会 NRAM 溢出
    • 计算 NRAM 使用量:TOTAL_IN * 4B + TOTAL_OUT * 4B
    • 思考:为什么需要将整个张量加载到 NRAM?能否直接在 GDRAM 上进行池化?
  • 使用 Dilation 的高级实现

    • __bang_maxpool API 不直接支持 dilation(膨胀)参数,如果需要实现 dilation,需要使用 __bang_maxpool_value_index API
    • 推荐学习路径:在掌握 __bang_maxpool 的基础上,学习如何使用 __bang_maxpool_value_index 实现支持 dilation 的 MaxPool2d
    • API 参考文档Cambricon BANG C - __bang_maxpool_value_index
    • 实验:阅读 __bang_maxpool_value_index 的文档,理解它返回最大值和索引的特性
    • 思考:为什么需要 __bang_maxpool_value_index?它在反向传播中有什么作用?

2. Tiling 实现(maxpooling_simple_tiling.mlu

  • 按照 tiling 策略,将 (batch, channel) 对分配给多个任务,每个任务处理多个 (batch, channel) 对;
  • Kernel 中根据 taskId 确定当前任务处理哪些 (batch, channel) 对,逐个加载到 NRAM 中进行池化,再写回 GDRAM;
  • NRAM 优化:每次只加载单个 (batch, channel) 对的数据,避免将整个张量加载到 NRAM,适合更大规模的数据;
  • 任务并行:使用多个任务并行处理不同的 (batch, channel) 对,提高整体性能;
  • Host 端设置 num_tasks、使用 BLOCK 模式,并通过 Notifier 记录耗时,同时调用 verify_rm_rm 验证结果。

探索任务 - 参数调优

参考文档:Cambricon BANG C/C++ 编程指南 - 任务映射

  • 任务分配策略理解 - 为什么按 (batch, channel) 分配任务?

    • 当前实现:每个任务处理 BC_PER_TASK 个 (batch, channel) 对,num_tasks = ceil(BATCH * CHANNELS / BC_PER_TASK)
    • 实验:尝试修改 BC_PER_TASK(1、2、4、8...),观察:
      • 任务数量如何变化?
      • NRAM 使用量如何变化?
      • 性能如何变化?
    • 观察:不同 BC_PER_TASK 下的负载均衡情况
    • 思考:为什么按 (batch, channel) 分配是合理的?这种分配方式如何保证数据独立性?
  • Tiling 大小的影响

    • 当前实现:NRAM_BUFFER_SIZE_IN = 64 * 64NRAM_BUFFER_SIZE_OUT = 32 * 32
    • 实验:尝试修改 IN_HIN_W(64 → 128 → 256...),观察:
      • NRAM 是否溢出?
      • 是否需要调整 NRAM_BUFFER_SIZE_INNRAM_BUFFER_SIZE_OUT
      • 性能如何变化?
    • 思考:如何根据硬件 NRAM 大小调整 tiling 参数?有没有自动计算 tiling 大小的方法?
  • 任务数量(dim)调优

    • 当前 num_tasks 根据 BC_PER_TASK 自动计算,设置 dim = {num_tasks, 1, 1}
    • 实验:尝试不同的 BC_PER_TASK,观察:
      • 任务数量如何变化?
      • 性能如何变化?
      • 是否存在最优的任务数量?
    • 思考:任务数量与硬件核心数的关系?是否存在最优的任务数量?
  • 任务类型(ktype)调优

    • Block 任务cnrtFuncTypeBlock):当前使用的任务类型
    • Union 任务cnrtFuncTypeUnion1/Union2/Union4 等):需要满足对齐约束
    • 实验:尝试将 cnrtFuncTypeBlock 改为 cnrtFuncTypeUnion1,观察:
      • 是否能正常编译和运行?
      • 如果出现 CN_ERROR_INVALID_VALUE 错误,检查 num_tasks 是否满足对齐要求
      • 性能是否有提升或下降?
    • 思考:对于 MaxPool2d 这种按 (batch, channel) 分配的任务,Union 类型是否合适?为什么?
  • 与最小实现的对比

    • 复杂度对比
      • Simple 版本:单个任务,整个张量加载到 NRAM,代码简单
      • Tiling 版本:多个任务,逐 (batch, channel) 加载,代码稍复杂
    • NRAM 使用对比
      • Simple 版本:TOTAL_IN * 4B + TOTAL_OUT * 4B(可能溢出)
      • Tiling 版本:NRAM_BUFFER_SIZE_IN * 4B + NRAM_BUFFER_SIZE_OUT * 4B(更可控)
    • 性能对比
      • 运行两个版本,对比执行时间
    • 适用场景对比
      • Simple 版本:小规模数据,学习 API 使用
      • Tiling 版本:大规模数据,实际生产使用
    • 思考:什么时候应该使用 Simple 版本?什么时候应该使用 Tiling 版本?
  • 综合调优:结合以上参数,尝试找到在保证正确性的前提下性能最优的配置组合。建议:

    1. 先理解 MaxPool2d 算法的基本原理(空间池化、滑动窗口)
    2. 理解 __bang_maxpool API 的参数含义和限制
    3. 理解任务分配策略(按 (batch, channel) 分配的优势)
    4. 根据数据规模选择合适的 tiling 参数
    5. 调整 BC_PER_TASK、任务类型等参数,观察性能变化
    6. 记录不同配置下的运行时间,找到最优组合

3. 进阶:使用 Dilation

__bang_maxpool API 不直接支持 dilation(膨胀)参数。如果需要实现 dilation,需要使用 __bang_maxpool_value_index API。

Dilation(膨胀)的作用

  • dilation > 1 时,池化窗口中的采样点之间有间隔,可以增大感受野而不增加参数数量
  • dilation = 1 时,等同于普通池化(无膨胀)
  • dilation 是卷积神经网络中重要的技术,尤其在语义分割等任务中应用广泛

__bang_maxpool_value_index API

  • API 参考文档:Cambricon BANG C - __bang_maxpool_value_index
  • 特点:返回池化后的值和索引(value + index)
  • 应用场景
    • 实现 dilation 支持
    • 支持反向传播(需要最大值的位置索引)
    • 更灵活的池化窗口处理

学习建议

  1. 先掌握 __bang_maxpool API 的使用(maxpooling_simple.mlumaxpooling_simple_tiling.mlu
  2. 阅读 __bang_maxpool_value_index 的官方文档,理解其参数含义
  3. 参考相关资料或示例代码,学习如何使用 __bang_maxpool_value_index 实现支持 dilation 的 MaxPool2d
  4. 理解 dilation 的工作原理(通过 ih = h_start + kh * dilation 控制采样间隔)
  5. 尝试修改代码,实现支持 dilation 的 MaxPool2d

实验建议

  • 尝试不同的 dilation 值(1、2、3...),观察:
    • 采样点如何变化(dilation=2 时,采样点之间有 1 个间隔)
    • 输出尺寸如何变化(使用输出尺寸公式计算)
    • 感受野如何增大
  • 对比 dilation=1(普通池化)和 dilation>1(膨胀池化)的效果

4. 运行脚本(build_eval.sh

运行脚本提供了完整的编译和执行环境,包含必要的环境变量设置和编译命令。

4.1 环境变量配置

脚本开头设置了以下环境变量,这些是运行 BangC 程序所必需的:

  • NEUWARE_HOME=/usr/local/neuware: 指定 Neuware SDK 的安装路径,Neuware 是寒武纪 MLU 的开发工具包
  • LD_LIBRARY_PATH: 添加 Neuware 的库文件路径($NEUWARE_HOME/lib64),确保运行时能找到 MLU 相关的动态链接库
  • PATH: 添加 Neuware 的二进制工具路径($NEUWARE_HOME/bin),使 cncc 编译器可以直接调用
  • MLU_VISIBLE_DEVICES=0: 指定使用第 0 号 MLU 设备(在多卡环境下可以选择其他设备)
  • TORCH_DEVICE_BACKEND_AUTOLOAD=0: 禁用 PyTorch 的设备后端自动加载,避免与 BangC 运行时冲突

4.2 使用方法

脚本接受一个参数:.mlu 源文件的文件名。

./build_eval.sh maxpooling_simple.mlu
./build_eval.sh maxpooling_simple_tiling.mlu

脚本会:

  1. 自动切换到脚本所在目录(Experiments/04_maxpooling
  2. 使用 cncc 编译器编译 .mlu 文件,生成可执行文件
  3. 执行生成的可执行文件并输出结果

4.3 编译参数说明

脚本使用的编译命令:

cncc "${MLU_SOURCE}" -o "${TARGET}" --bang-mlu-arch=mtp_592 -O3 -lm
  • --bang-mlu-arch=mtp_592: 指定目标 MLU 架构为 mtp_592
  • -O3: 最高级别的优化
  • -lm: 链接数学库

5. 建议的学习流程

  1. 先用最小实现理解 __bang_maxpool API 的基本用法,通过 build_eval.sh maxpooling_simple.mlu 编译运行并验证正确性;
  2. 理解 __bang_maxpool API 的参数含义和输出尺寸计算;
  3. 进行最小实现的探索任务(API 理解、数据布局、输出尺寸计算、NRAM 使用分析);
  4. 用 tiling 实现理解任务并行和 NRAM 优化,通过 build_eval.sh maxpooling_simple_tiling.mlu 编译运行并验证正确性;
  5. 理解 tiling 策略(按 (batch, channel) 分配任务、NRAM 缓冲优化);
  6. 对比最小实现和 tiling 实现的性能差异,理解各自的适用场景;
  7. 进行 tiling 实现的参数调优(任务分配策略、tiling 大小、任务数量、任务类型),逐步调整参数,观察性能变化并记录耗时;
  8. 阅读 __bang_maxpool_value_index 的官方文档,理解 dilation 的实现原理;
  9. 尝试实现支持 dilation 的 MaxPool2d(作为进阶练习)。

6. 与其他算子的对比

  • VecAdd:一维向量,逐元素操作,按元素切分任务
  • ReLU:一维向量,逐元素激活,按元素切分任务,支持 in-place 优化
  • Softmax:二维矩阵,多遍扫描,按行分配任务
  • MaxPool2d:四维张量,空间池化,按 (batch, channel) 分配任务,使用 __bang_maxpool API

思考:为什么 MaxPool2d 适合按 (batch, channel) 分配任务?这种分配方式如何保证数据独立性?与其他算子的任务分配策略有何不同?