机器之心矩阵相乘在GPU上的终极优化:深度解析Maxas汇编器工作原理( 七 )
1,0,2,3,5,4,6,7, 33, 32, 34, 35, 37, 36, 38, 39,45, 44, 46, 47, 41, 40, 42, 43, 13, 12, 14, 15,9,8, 10, 11,17, 16, 18, 19, 21, 20, 22, 23, 49, 48, 50, 51, 53, 52, 54, 55,61, 60, 62, 63, 57, 56, 58, 59, 29, 28, 30, 31, 25, 24, 26, 27
按照文档中的说法 , 每个操作数有 8 字节的重用缓存可以缓存两个 4 字节寄存器 , 而逐行遍历只用了其中一个用于缓存 A 寄存器的数据 , 所以缓存使用率偏低 。 我推测考虑到有 B 操作数也有重用缓存但没有利用 , 逐行遍历的重用缓存利用率为 4/8/2=25% 。 对于来回遍历的利用率估算不是那么直观 , 文档直接给出了其利用率为 39% , 而漩涡遍历的利用率可以达到 49% 。 从 maxas 最后生成的汇编代码来看其中有的指令确实对 A 和 B 同时使用了重用缓存 , 同时在对每个操作数缓存了两个寄存器:
--:-:-:-:1FFMA R37, R71.reuse, R72.reuse, R37;--:-:-:-:1FFMA R36, R71.reuse, R73.reuse, R36;#前2条指令对第3个操作数缓存了R72和R73 , 它们被接下来的2条指令用到--:-:-:-:1FFMA R38, R69.reuse, R73, R38; --:-:-:-:1FFMA R39, R69.reuse, R72, R39;#前4条指令对第2个操作数缓存了R69和R71 , 它们被接下来的4条指令用到--:-:-:-:1FFMA R45, R71.reuse, R74.reuse, R45;--:-:-:-:1FFMA R44, R71, R75.reuse, R44;--:-:-:-:1FFMA R46, R69.reuse, R75.reuse, R46;--:-:-:-:1FFMA R47, R69, R74.reuse, R47;
不过 , 在来回遍历可以完全解决 bank 冲突的情况下依然试图提高重用缓存的使用率的目的并不在于提高重用率 , 而且因为 FFMA 指令之间插入的从共享内存到寄存器的载入指令 。 这样做的目的是为了载入指令的延迟可以被不依赖于其数据的计算指令所填充 。 遍历 C 矩阵寄存器的前八条指令和其间插入的载入指令如下:
01:-:-:-:0FFMA R1, R66.reuse, R72.reuse, R1;--:-:-:-:1LDS.U.128 R80, [R106+0x200];--:-:-:-:1FFMA R0, R66, R73.reuse, R0;--:-:-:-:0FFMA R2, R64.reuse, R73.reuse, R2;--:-:-:-:1LDS.U.128 R88, [R107+0x200];--:-:-:-:1FFMA R3, R64, R72.reuse, R3;--:-:-:-:0FFMA R5, R67.reuse, R72.reuse, R5;--:-:-:-:1LDS.U.128 R84, [R106+0x300];--:-:-:-:1FFMA R4, R67, R73.reuse, R4;--:-:-:-:0FFMA R6, R65.reuse, R73.reuse, R6;--:-:1:-:1LDS.U.128 R92, [R107+0x300];--:-:-:-:1FFMA R7, R65, R72.reuse, R7;
由于载入指令的运行时间需要 20 多个时钟周期(对应于共享内存的延迟) , 在这段时间内其第一个的操作数都有可能被通过 bank 访问到 , 此时有可能其后的计算指令也被发射并需要访问同一个 bank , 这就造成了一个延迟的 bank 冲突 。 不过这只是一个基本原理 , maxas 的遍历顺序是如何具体避免这样的 bank 冲突目前还没有完全搞清楚 。
从本节可以看出即使所有计算全部在寄存器中进行 , 还是要用到两个技巧来得到最佳性能:
1. 最优化的寄存器编号
2. 最优化的遍历顺序
至于计算本身已经显得如此简单以至于 maxas 文档都懒得提了 。
传输 C 矩阵到主显存
每个线程块完成所负责的那个小片矩阵的计算后 , 最后一个任务就是将其从寄存器传输到主显存 。 由于寄存器无法在线程间共享(其实有_shfl_sync() 指令可以但是此处不适用) , 所以每个线程必须将所计算的 4 个矩阵先传输至共享内存 。 之所以不从寄存器直接传输到主显存是因为按照现有的线程布局无法利用 GPU 的超大带宽 。 要充分利用 GPU 的带宽我们希望每个 warp 的 32 个线程所同时传输的数据是连续的 , 这样一个时钟周期里就可以一下子传输 128 字节的数据 , 如果这些数据离得太远 , 在最坏可能下需要分 32 次才能传输出去 。
根据图 2 的线程布局 , 每一列连续的 64 字节数据分布在 8 个线程中 , 比如第 1 列前 4 行的结果都保存在线程 0,2,4,6,8,10,12,14 所控制的寄存器中 , 每个线程在该行有 8 个寄存器 , 而且为了避免 bank 冲突这 8 个寄存器都不是连续的 , 因此不能使用向量传输指令 , 所以需要分 8 次才能完成一个 warp 的传输 , 而此时 warp 中的其他 24 个线程将无所事事 , 因为其数据都不在这一列上 。 为了解决这个问题可以首先利用共享内存暂存所有线程的数据 , 然后用令一种线程布局将共享内存中的连续的数据同时传输出去 。
推荐阅读
- 机器人|深圳机器人产业产值1257亿元
- |《5G技术助力国产机器人完成全球首场骨科实时远程手术》公示材料
- 美军事进行时|五角大楼研制挖隧道的蚯蚓机器人为地面部队提供安全补给
- cnBetaTB|看机器人如何制作出既有颜值又美味的蛋饼
- 山东伟豪思|袋料全自动拆垛机器人的使用给企业带来了哪些益处
- 无人机这两项机器人发明,就是东京大学进军外卖界的野心!?
- 搜狐新闻|【复材资讯】碳纤维机器人手臂设计需要考虑的要素
- SILVER六足龙虾机器人成海底“清洁工”,可下潜200米续航16小时
- 新智元|机器学习团队常用工具总结,人生苦短,我用Python!
- 机器人5G+AI助力科技抗疫 各路机器人大显身手
