3.2 KiB
3.2 KiB
步骤 3:C++ 层优化与 oracle
目标:在进入 asm 前,把语义、layout、内存和调度问题先在 C++ 层解决。
3.1 CoreFunctions 复用
优先替换能安全复用的循环:
| 代码模式 | 可考虑函数 |
|---|---|
| 大规模 GEMM | MNNPackedMatMul |
| E=1 GEMV | MNNComputeMatMulForE_1 |
| scale+bias | MNNScaleAndAddBias / MNNScaleAndAddBiasScalar |
| softmax/norm/activation | MNNSoftmax / MNNNorm / MNNExp / MNNSiLu |
| pack/unpack | 对应 gcore pack helpers |
每处替换都要检查:
- 数学语义、layout、转置和 tail 是否一致。
dst == src是否安全;不确定就读实现或加小测试。- 小 shape 下函数调用和 pack 开销是否值得。
- 保留手写循环时,原因是否清楚。
3.2 C++ 标量 oracle
复杂 kernel 必须先写标量 oracle。oracle 可以是临时 debug 代码,也可以是测试中的 reference。
低 bit oracle 要显式表达:
- bit unpack。
- zero point / scale / bias。
- block metadata。
- fp32 min/max。
- add-dst。
- tail 和 OC split。
建议分层比较:
| 输出 | 目的 |
|---|---|
| unpack int weight | 查 bit layout |
| int32 accumulator | 查 dot/smmla 分组 |
| dequant FP32 | 查 scale/zp |
| final dst | 查 postprocess |
3.3 C++ SIMD/寄存器模拟
当目标是 w2/w3、sdot、smmla、SME2 或复杂 pack/tail 时,写 C++ 模拟版再迁移 asm。
要求:
- 用局部数组或
std::array表达 lane,不依赖编译器自动向量化。 - helper 名称对应目标指令,例如
simulateSdot4、simulateSmmla、unpackW3Block64。 - 变量名尽量对应未来 asm 寄存器角色,例如
vAcc0、vInput0、vMin。 - 每次改变 unroll、pack layout 或 block64 分支,先让模拟版和标量 oracle 对齐。
简单 elementwise NEON 不强制模拟;小 reference + asm register plan 通常足够。
3.4 内存和线程
检查项:
- onExecute 中不要反复
new、malloc或大std::vector临时缓存。 - 需要 scratch 时,在 onResize 中用 Backend 内存池申请。
- 多线程按独立输出区域划分,避免写冲突和 false sharing。
- OC split 的 per-thread pointer 要按真实 packed cell stride 计算。
- 线程数、tile 数、block 数不要在内层重复计算。
不要过早做大小核特化;先让 runtime 现有线程策略和目标 kernel 路径正确。
3.5 C++ 阶段验证
每个小改动后优先跑 focused test:
cd build
cmake --build . -j 8
./run_test.out op/lowMemory/blockConv 0 1 4
./run_test.out op/lowMemory/HybridConv 0 1 4
./run_test.out op/lowMemory/blockConv 0 1 4 # 在 SDOT 目标设备/构建配置上复跑
./run_test.out op/lowMemory/HybridConv 0 1 4 # 在 SDOT 目标设备/构建配置上复跑
如果不是低 bit LLM kernel,替换为对应算子的最小 op test。
通过标准
- 可复用的 CoreFunctions 已复用,或已说明不复用原因。
- C++ 标量 oracle 可运行,复杂 SIMD 语义已有 C++ 模拟。
- 没有引入 onExecute 大临时分配。
- 线程拆分和 pointer offset 已覆盖非 0 线程。
- focused correctness test 通过后,再进入 asm。