关于mindspore 原地更新类算子的一点思考记录如下:
现象记录:
原始测试代码
错误结果复现:
分析:
如果在场景中加入42行的copy()操作此时cpu的结果就会正确,但是gpu的结果则不受copy做操作的影响
修改后测试结果:
Mindspore 控制流场景中,我们的H是由mnp.one 新建出来的,但是其底层是调用的numpy的封装申请出来的是一个const的数据,这样的数据通过赋值给H,如果H在传递到其他地方,其内部被修改,下次在赋值,这块const的地址不会新创建,那么下次循环H的
值就会不对了。
解决办法:
打開42行copy的注釋
將k 以及 k_iter的值從tensor改成数值0,这样while控制流会将这些body 展开,相当于 H的值存储了多份也不会有任何更改
查看tensorscatterNdUpdate 后端实现,确定其实现是否是直接修改输入并且返回输入的操作
结果发现:其实现是在input的基础上inplace操作然后拷贝到输出地址,这样修改了原先的干净的输入地址,倘若存在内存复用其生命周期还未结束,那么其他地方拿到的值就会被污染,因此修改实现为拷贝输入到输出然后在做inplace 操作,修改后代码,这样不论输入的生命周期是否结束,均不会影响到原来的值
总结:
1.为什么静态图下会出错?
动态图中不会有静态的推断,H的值每次都会新赋值不同地址,因此上次的H的地址内容被修改,不会有影响
为什么cpu会出错 gpu 不出错?
因为cpu不存在host和device的概念,在图运行时cpu inplace类算子做相关操作时直接污染了cpu的输入原始地址,但是gpu操作时会先将host侧内容拷贝到device侧操作那么device侧输入原始地址被污染没关系,下次循环子图时会有新的地址过来,无影响。
解决办法加copy操作好还是直接修改后端算子好?
3.1. 直接加copy会导致某一后端正确(cpu)但某一后端无影响是冗余操作,不妥且只是规避并没有解决这个问题。
3.2. 直接修改后端算子的话除了能解决当前场景的问题,也能避免内存复用引用计数时因为输入的生命周期过长,而输入的原始地址被污染产生更难定位的问题。
3.3. 同时也能知道mindspore的机制在后端是不支持直接的inplace操作的,一切对输入的inplace操作,都存在一定的隐患,因此编写算子过程中不光cpu 其他gpu ascend,均应该先将输入拷贝到输出,再在输出中做inplace 操作,不管要保证输出地址的合法性也要保证输入地址没有被污染。因此编写原地更新操作类算子应该遵循上述准则才可以。
对应pr 修改:
https://gitee.com/mindspore/mindspore/pulls/27203/files
https://bbs.huaweicloud.com/forum/forum.php?mod=viewthread&tid=171550&fromuid=642331