本目录围绕 vecadd.mlu 展示了两个学习阶段的程序、一个运行脚本和如何快速上手的说明。所有示例都在 Experiments/01_vecadd 下,可以直接参考对应文件。
- 使用单个任务,通过 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 容量和对齐要求会如何变化?代码是否需要修改?
- 按照 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改为cnrtFuncTypeUnion1或cnrtFuncTypeUnion2,观察:- 是否能正常编译和运行
- 如果出现
CN_ERROR_INVALID_VALUE错误,检查taskDimX是否满足对齐要求 - 性能是否有提升或下降
- 任务是否被展开到多个 Cluster 上执行
- Block 任务(
-
任务数量(dim)调优:
- 当前
num_tasks根据ELEM_SLICE自动计算,设置dim = {num_tasks, 1, 1} - 对于 Block 任务:尝试不同的
taskDimX(1、2、4、8、16...),观察性能变化 - 对于 Union 任务:必须确保
taskDimX是clusterDim * coreDim的正整数倍 - 思考:任务数量与硬件核心数(Cluster 数量、每个 Cluster 的 Core 数量)的关系如何?是否存在最优的任务数量?驱动如何决定是否展开任务?
- 当前
-
任务映射理解:
- Block 任务:可以灵活映射到任意空闲的 MLU Core,适合小规模或灵活性要求高的场景
- Union 任务:需要满足对齐约束,适合大规模并行计算,可以充分利用多个 Cluster 的并行能力
- 在有限硬件资源情况下,Union 任务可能需要等待足够的 Cluster 空闲才能执行
- 驱动会根据硬件实时利用率决定是否展开任务,尽可能占满所有硬件资源
-
综合调优:结合以上参数,尝试找到在保证正确性的前提下性能最优的配置组合。建议:
- 先理解当前硬件配置(Cluster 数量、每个 Cluster 的 Core 数量)
- 根据数据规模选择合适的任务类型
- 调整 tiling 大小和任务数量,平衡内存占用和并行度
- 记录不同配置下的运行时间,找到最优组合
运行脚本提供了完整的编译和执行环境,包含必要的环境变量设置和编译命令。
脚本开头设置了以下环境变量,这些是运行 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 运行时冲突
使用 cnmon 命令可以查看 MLU 板卡的实时状态(类似于 NVIDIA 的 nvidia-smi):
cnmon -smi该命令会显示:
- MLU 设备型号和计算能力
- 当前使用率和温度
- 内存使用情况
- 功耗信息
建议在运行程序前后查看设备状态,以监控资源使用情况和程序执行效果。
脚本接受一个参数:.mlu 源文件的文件名。
./run_vecadd.sh vecadd.mlu
./run_vecadd.sh vecadd_minimal.mlu脚本会:
- 自动切换到脚本所在目录(
Experiments/01_vecadd) - 使用
cncc编译器编译.mlu文件,生成可执行文件 - 执行生成的可执行文件并输出结果
脚本使用的编译命令:
cncc "${MLU_SOURCE}" -o "${TARGET}" --bang-mlu-arch=mtp_592 -O3 -lm--bang-mlu-arch=mtp_592: 指定目标 MLU 架构为 mtp_592-O3: 最高级别的优化-lm: 链接数学库
- 先用最小实现确认 kernel、host、编译链的基本流程,通过
run_vecadd.sh vecadd_minimal.mlu编译运行并验证正确性; - 在
vecadd.mlu中跟踪 tiling 逻辑,重点关注 NRAM 缓冲与 task 切分的实现,通过run_vecadd.sh vecadd.mlu编译运行并验证正确性; - 进行最小实现的探索任务(上限和下限探索),理解 NRAM 容量限制和对齐要求;
- 进行 tiling 实现的参数调优,逐步调整
TILE_ELEM/ELEM_SLICE、任务类型、任务数量等参数,观察性能变化并记录耗时。