ksco 的工作日志
怎么回事,半个月没做,刚刚怎么复现不了这条日志了
具体的问题是在一个奇怪的地方 assert 失败了,debug 发现竟然是 build system 的问题,我就觉得事有蹊跷。
现在猜测大概问题是因为 ccache 和 cmake 没有配合好(?)。加上现在某个 python 生成的 C header 文件中的枚举值不太 reproducible,导致不同的 compile unit #include 了不同版本的头文件(?)。都是猜测, dr 的 build system 过于复杂,先不 debug 了,重新编译后暂时正常了。
现在猜测大概问题是因为 ccache 和 cmake 没有配合好(?)。加上现在某个 python 生成的 C header 文件中的枚举值不太 reproducible,导致不同的 compile unit #include 了不同版本的头文件(?)。都是猜测, dr 的 build system 过于复杂,先不 debug 了,重新编译后暂时正常了。
如何用一天的时间实现 numpy RVV 支持
背景:
numpy 的基本架构是有一套 universal intrinsic,然后各个后端(比如 Neon、SSE)用自己的 intrinsic 去实现这个 universal 的。
方案一:
所以如果要增加 RVV 的支持,比较正统的做法就是增加一个 RVV 的后端,然后去实现这套 universal intrinsic。
但就目前情况来说,上游自己也觉得维护这么多后端的担子太重了,所以他们其实有砍掉整个 universal intrinsic,换用更加成熟的 google/highway 的想法。
这也决定了上游几乎不太可能接受一个巨大的 RVV backend patch 了,方案一属于费力不讨好。
方案二:
既然上游有用 highway 的意愿,那就帮助上游把所有用 universal intrinsic 写的函数都用 highway 重写一遍,因为 highway 支持 RVV,这样 numpy RVV 也就顺手支持了。
方案二最大的问题就是上游推进 highway 支持的进度非常慢,这对其他已有的后端几乎没有影响,但对于 RVV 来说就是有还是无的致命问题。
方案三:
那如果放弃提交上游,有没有更加快捷,不需要完整实现一个 RVV 后端的方法呢?
方案三就是今天要介绍的做法,neon2rvv[1] 是一个将 ARM Neon intrinsic 翻译成 RVV intrinsic 的兼容层,举个例子:
可以看到
因为 Neon 的寄存器是 128 位的,这就要求 vlen 必须大于等于 128 位,并且超出 128 位的部分会被完全浪费掉。
本着有总比没有强的原则,使用这个兼容层,我们就可以原地利用 Neon 后端来实现 RVV 后端了,理想情况下,要做的就只是
当然,理想情况并不存在,在实际的实现过程中,我发现了一些无法用 RVV 实现的 Neon intrinsic;neon2rvv 尚未实现的 intrinsic;neon2rvv bug 等等。
最终的结果就是:
numpy 加了一个很小的 patch [2],neon2rvv 提交了一个很小的 PR [3],numpy RVV 就有了初步的支持。
在新发布的 BananaPi F3(顺序 8 核,vlen=256) 上运行 pytest 结果如下:
可以看到,目前的支持度还是挺不错的,失败的都是一些比较极端的情况,还需要花更多时间来 debug。
做了一个简单的 benchmark,在
[1] https://github.com/howjmay/neon2rvv
[2] https://github.com/plctlab/numpy/commit/edee45e5e184d23929a605c9441607aca03a019a
[3] https://github.com/howjmay/neon2rvv/pull/396
背景:
numpy 的基本架构是有一套 universal intrinsic,然后各个后端(比如 Neon、SSE)用自己的 intrinsic 去实现这个 universal 的。
方案一:
所以如果要增加 RVV 的支持,比较正统的做法就是增加一个 RVV 的后端,然后去实现这套 universal intrinsic。
但就目前情况来说,上游自己也觉得维护这么多后端的担子太重了,所以他们其实有砍掉整个 universal intrinsic,换用更加成熟的 google/highway 的想法。
这也决定了上游几乎不太可能接受一个巨大的 RVV backend patch 了,方案一属于费力不讨好。
方案二:
既然上游有用 highway 的意愿,那就帮助上游把所有用 universal intrinsic 写的函数都用 highway 重写一遍,因为 highway 支持 RVV,这样 numpy RVV 也就顺手支持了。
方案二最大的问题就是上游推进 highway 支持的进度非常慢,这对其他已有的后端几乎没有影响,但对于 RVV 来说就是有还是无的致命问题。
方案三:
那如果放弃提交上游,有没有更加快捷,不需要完整实现一个 RVV 后端的方法呢?
方案三就是今天要介绍的做法,neon2rvv[1] 是一个将 ARM Neon intrinsic 翻译成 RVV intrinsic 的兼容层,举个例子:
uint32x2_t vadd_u32(uint32x2_t a, uint32x2_t b) {
return __riscv_vadd_vv_u32m1(a, b, 2);
}
可以看到
vadd_u32 和 __riscv_vadd_vv_u32m1 是一对一的翻译,当然也存在一对多的情况。因为 Neon 的寄存器是 128 位的,这就要求 vlen 必须大于等于 128 位,并且超出 128 位的部分会被完全浪费掉。
本着有总比没有强的原则,使用这个兼容层,我们就可以原地利用 Neon 后端来实现 RVV 后端了,理想情况下,要做的就只是
#include neon2rvv.h 。当然,理想情况并不存在,在实际的实现过程中,我发现了一些无法用 RVV 实现的 Neon intrinsic;neon2rvv 尚未实现的 intrinsic;neon2rvv bug 等等。
最终的结果就是:
numpy 加了一个很小的 patch [2],neon2rvv 提交了一个很小的 PR [3],numpy RVV 就有了初步的支持。
在新发布的 BananaPi F3(顺序 8 核,vlen=256) 上运行 pytest 结果如下:
237 failed, 42599 passed
可以看到,目前的支持度还是挺不错的,失败的都是一些比较极端的情况,还需要花更多时间来 debug。
做了一个简单的 benchmark,在
absolute_f32 上对比标量有 4 倍的性能提升。因为这台机器的 vlen 是 256,所以预期值应该是 8 倍,这里 4 倍是因为高 128 位完全没利用起来。[1] https://github.com/howjmay/neon2rvv
[2] https://github.com/plctlab/numpy/commit/edee45e5e184d23929a605c9441607aca03a019a
[3] https://github.com/howjmay/neon2rvv/pull/396
👍6