什么是 SideEffect
在计算机科学中,函数副作用(side effect)指当调用函数时,除了返回可能的函数值之外,还对主调用函数产生附加的影响。例如修改全局变量(函数外的变量),修改参数,向主调方的终端、管道输出字符或改变外部存储信息等。
—— WikiPedia 副作用_(计算机科学)
为什么需要在 PaddleSOT 中使用 SideEffect 我们先来看一个demo, 我们可以看到此时的的SOT
并不能准确的还原global
并产生副作用.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 from sot.translate import symbolic_translateglobal_x = 1 def foo (): global global_x global_x = global_x + global_x return global_x def main (): dygraph_out = foo symbolic_translate_out = symbolic_translate(foo) print ("symbolic_translate_out:" , symbolic_translate_out()) print ("symbolic_translate_out:" , symbolic_translate_out()) print ("dygraph_out:" , dygraph_out()) print ("dygraph_out:" , dygraph_out())
原生字节码:
1 2 3 4 5 6 7 10 0 LOAD_GLOBAL 0 (global_x) 2 LOAD_GLOBAL 0 (global_x) 4 BINARY_ADD 6 STORE_GLOBAL 0 (global_x) 11 8 LOAD_GLOBAL 0 (global_x) 10 RETURN_VALUE
转写后字节码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 8 0 LOAD_GLOBAL 1 (paddle_set_eval_frame_fn) 2 LOAD_CONST 0 (None) 4 CALL_FUNCTION 1 6 STORE_FAST 0 (___old_eval_frame) 8 LOAD_GLOBAL 2 (__compiled_fn_dummy_func) 10 BUILD_TUPLE 0 12 CALL_FUNCTION 1 14 UNPACK_SEQUENCE 0 16 LOAD_GLOBAL 0 (global_x) 18 LOAD_GLOBAL 1 (paddle_set_eval_frame_fn) 20 LOAD_FAST 0 (___old_eval_frame) 22 CALL_FUNCTION 1 24 POP_TOP 26 RETURN_VALUE
如何实现 SideEffect 其实实现起来也非常简单, 我们只需要重新把数据通过SideEffect
, 机制重新给到python的栈结构上就能保证数据的准确性
转写后字节码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 8 0 LOAD_GLOBAL 1 (paddle_set_eval_frame_fn) 2 LOAD_CONST 0 (None) 4 CALL_FUNCTION 1 6 STORE_FAST 0 (___old_eval_frame) 8 LOAD_GLOBAL 2 (__compiled_fn_dummy_func) 10 BUILD_TUPLE 0 12 CALL_FUNCTION 1 14 UNPACK_SEQUENCE 0 16 LOAD_CONST 1 (4) 18 LOAD_CONST 1 (4) 20 STORE_GLOBAL 0 (global_x) 22 LOAD_GLOBAL 1 (paddle_set_eval_frame_fn) 24 LOAD_FAST 0 (___old_eval_frame) 26 CALL_FUNCTION 1 28 POP_TOP 30 RETURN_VALUE
如何知道需要重新加载哪些数据呢 ? 这里是通过STORE_GLOBAL
这个字节码来判断是否更新数据的,只有当global
变量被更新的时候,我们才会重新加载数据.
当运行到STORE_GLOBAL
时会将GlobalVariable
中原有的数据更新, 在更新的同时GlobalVariable
中的数据域MutableDictLikeData
会记录下来更新的数据, 以及版本信息, 以便后续重新加载数据.
如何重新加载数据呢 ? 在FunctionGraph
类中的restore_side_effects
方法中实现副作用机制的还原, 通过MutableDictLikeData
中的数据来还原GlobalVariable
中的数据, 以及版本信息.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 if isinstance (var, GlobalVariable): for record in var.proxy.get_last_records(): if isinstance (record, (MutationSet, MutationNew)): record.value._reconstruct(self .pycode_gen) self .restore_side_effects(variables[1 :]) for record in var.proxy.get_last_records()[::-1 ]: if isinstance (record, (MutationSet, MutationNew)): self .pycode_gen.gen_store_global(record.key) if isinstance (record, MutationDel): self .pycode_gen.gen_delete_global(record.key)
这里store
应该要反着才能对应上load
的顺序
1 2 3 4 5 6 7 load 1 load 2 下一个 var store 2 store 1
至此,就完成了整个global
副作用的实现.
参考链接