Skip to content

Latest commit

 

History

History
151 lines (113 loc) · 9.42 KB

File metadata and controls

151 lines (113 loc) · 9.42 KB

VecAdd 初学者教程

本目录围绕 vecadd.mlu 展示了两个学习阶段的程序、一个运行脚本和如何快速上手的说明。所有示例都在 Experiments/01_vecadd 下,可以直接参考对应文件。

1. 最小实现(vecadd_minimal.mlu

  • 使用单个任务,通过 BangC API(__memcpy__bang_add)完成向量加法 dst[i] = a[i] + b[i]
  • 不做 tiling:假设数据量较小(1024 个元素),一次性将所有数据加载到 NRAM 中处理,然后写回 GDRAM;
  • 主机端准备随机输入、分配设备内存、拷贝数据、执行 Kernel,然后拷贝回主机并与 CPU 参考结果比对;
  • 适合初学者理解 BangC 编译器的基本编译/执行流程和 NRAM/GDRAM 的数据流动。

探索任务

参考文档:Cambricon BANG C/C++ 编程指南 - 硬件实现

  • 上限探索 - NRAM 容量限制

    • 代码中定义了 ELEM_COUNT 为 1024,NRAM 数组大小也固定为 1024。尝试逐步增大这个值(例如改为 2048、4096、8192...),观察程序是否仍能正常编译和运行。
    • 你会发现这个值存在一个上限,超过这个上限会导致编译错误(NRAM 溢出)或运行时问题。
    • 硬件背景:根据 MLU 的计算能力,NRAM 容量不同:
      • MLUv02(计算能力 2.0):NRAM 容量为 512KB
      • MLUv03(计算能力 3.0):NRAM 容量为 512KB 或 768KB(取决于架构号)
    • 思考:为什么会有这个上限?它与 MLU 的 NRAM 大小有什么关系?每个 float 占 4 字节,1024 个 float 需要 4KB,那么 NRAM 最多能容纳多少个 float?这就是为什么需要 tiling 的原因。
  • 下限探索 - 对齐要求

    • 尝试逐步减小 ELEM_COUNT(例如改为 512、256、128、64、32、16...),观察程序是否仍能正常编译和运行。
    • 你会发现这个值存在一个下限,低于这个下限会导致编译错误或运行时问题。
    • 硬件背景:MLU 硬件对向量运算有对齐要求:
      • 地址对齐粒度:向量运算的地址必须对齐到特定字节边界
        • MLUv02/v03(计算能力 2.0/3.0):64 字节对齐
        • MLUv03(某些架构):字节对齐(B)
      • 长度对齐粒度:向量运算的长度必须对齐到特定字节边界
        • MLUv02/v03(计算能力 2.0/3.0):128 字节对齐
        • MLUv03(某些架构):sizeof(T) 对齐(T 为数据类型)
    • 思考:为什么会有这个下限?它与 MLU 硬件特性(如内存对齐要求、指令对齐要求等)有什么关系?这个下限通常是多少?对于 float 类型(4 字节),128 字节对齐意味着至少需要多少个元素?
  • 存储层次理解

    • MLU 的存储层次从快到慢依次为:GPR(通用寄存器)→ NRAM(核内高速内存)→ WRAM(写回内存)→ SRAM(共享内存)→ L2 Cache → LDRAM(本地内存)→ GDRAM(全局内存)
    • 在最小实现中,数据从 GDRAM 加载到 NRAM,计算完成后写回 GDRAM
    • 思考:为什么不能直接在 GDRAM 上进行计算?NRAM 和 GDRAM 的访问速度差异有多大?这如何影响性能?
  • 计算能力差异

    • 不同 MLU 架构的计算能力不同,影响 NRAM 容量、对齐要求等特性
    • 当前代码使用 --bang-mlu-arch=mtp_592 编译,对应 MLUv03 架构
    • 思考:如果更换到其他架构(如 mtp_220、mtp_270、mtp_290、mtp_372),NRAM 容量和对齐要求会如何变化?代码是否需要修改?

2. Tiling 实现(vecadd.mlu

  • 按照 16K 的 TILE_ELEM 和 256K 的 ELEM_SLICE 将向量切成多个 tile,通过 NRAM 中的 nram_a/b/c 缓冲来避免频繁访问 GDRAM;
  • Kernel 中根据 taskId 分配连续 chunk,逐 tile 加载、执行 __bang_add,再写回 GDRAM;
  • Host 端设置 num_tasks、使用 BLOCK 模式避免 Union 对齐限制,并通过 Notifier 记录耗时,同时调用 verify_rm_rm 验证结果。

探索任务 - 参数调优

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

  • Tiling 大小调优:尝试修改 TILE_ELEM(当前为 16384)和 ELEM_SLICE(当前为 262144),观察不同组合对性能的影响。较大的 tile 可能提高计算效率,但会占用更多 NRAM;较小的 tile 可能增加内存访问次数。记录不同配置下的运行时间,找到性能最优的组合。

  • 任务类型(ktype)调优

    • Block 任务cnrtFuncTypeBlock):当前使用的任务类型。Block 任务的最小并行度为 1,只要有一个 MLU Core 空闲即可下发任务。驱动会根据硬件利用率决定是否将任务展开到多个 Core 上并行执行。迭代次数 = taskDimZ * taskDimY * taskDimX
    • Union 任务cnrtFuncTypeUnion1/Union2/Union4 等):UnionN 类型的任务需要满足以下约束:
      • 需要至少有 N 个 Cluster 空闲时才能下发任务
      • 起始物理 Cluster ID 必须能被 N 整除(对齐约束)
      • taskDimX % (N * coreDim) = 0 必须成立
      • 最小并行度为 clusterDim * coreDim
      • 迭代次数 = taskDimZ * taskDimY * taskDimX / (N * coreDim)

    尝试将 cnrtFuncTypeBlock 改为 cnrtFuncTypeUnion1cnrtFuncTypeUnion2,观察:

    • 是否能正常编译和运行
    • 如果出现 CN_ERROR_INVALID_VALUE 错误,检查 taskDimX 是否满足对齐要求
    • 性能是否有提升或下降
    • 任务是否被展开到多个 Cluster 上执行
  • 任务数量(dim)调优

    • 当前 num_tasks 根据 ELEM_SLICE 自动计算,设置 dim = {num_tasks, 1, 1}
    • 对于 Block 任务:尝试不同的 taskDimX(1、2、4、8、16...),观察性能变化
    • 对于 Union 任务:必须确保 taskDimXclusterDim * coreDim 的正整数倍
    • 思考:任务数量与硬件核心数(Cluster 数量、每个 Cluster 的 Core 数量)的关系如何?是否存在最优的任务数量?驱动如何决定是否展开任务?
  • 任务映射理解

    • Block 任务:可以灵活映射到任意空闲的 MLU Core,适合小规模或灵活性要求高的场景
    • Union 任务:需要满足对齐约束,适合大规模并行计算,可以充分利用多个 Cluster 的并行能力
    • 在有限硬件资源情况下,Union 任务可能需要等待足够的 Cluster 空闲才能执行
    • 驱动会根据硬件实时利用率决定是否展开任务,尽可能占满所有硬件资源
  • 综合调优:结合以上参数,尝试找到在保证正确性的前提下性能最优的配置组合。建议:

    1. 先理解当前硬件配置(Cluster 数量、每个 Cluster 的 Core 数量)
    2. 根据数据规模选择合适的任务类型
    3. 调整 tiling 大小和任务数量,平衡内存占用和并行度
    4. 记录不同配置下的运行时间,找到最优组合

3. 运行脚本(run_vecadd.sh

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

3.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 运行时冲突

3.2 查看 MLU 设备状态

使用 cnmon 命令可以查看 MLU 板卡的实时状态(类似于 NVIDIA 的 nvidia-smi):

cnmon -smi

该命令会显示:

  • MLU 设备型号和计算能力
  • 当前使用率和温度
  • 内存使用情况
  • 功耗信息

建议在运行程序前后查看设备状态,以监控资源使用情况和程序执行效果。

3.2 使用方法

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

./run_vecadd.sh vecadd.mlu
./run_vecadd.sh vecadd_minimal.mlu

脚本会:

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

3.3 编译参数说明

脚本使用的编译命令:

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

4. 建议的学习流程

  1. 先用最小实现确认 kernel、host、编译链的基本流程,通过 run_vecadd.sh vecadd_minimal.mlu 编译运行并验证正确性;
  2. vecadd.mlu 中跟踪 tiling 逻辑,重点关注 NRAM 缓冲与 task 切分的实现,通过 run_vecadd.sh vecadd.mlu 编译运行并验证正确性;
  3. 进行最小实现的探索任务(上限和下限探索),理解 NRAM 容量限制和对齐要求;
  4. 进行 tiling 实现的参数调优,逐步调整 TILE_ELEM/ELEM_SLICE、任务类型、任务数量等参数,观察性能变化并记录耗时。