def create_convert_shape_node(var_shape_node, slice_node=None, in_control_flow=False): assert isinstance(var_shape_node, (gast.Attribute, gast.Subscript)) if isinstance(var_shape_node, gast.Attribute): args = [ast_to_source_code(var_shape_node.value).strip()] # (1) A slice can be a simple number such as 1, -2, i.e. gast.Index or gast.Constant # (2) A slice can also be represented by bounds such as 2:-1, i.e. not gast.Index or gast.Constant # In (1) case, we pass the number as 'idx' argument in convert_var_shape # In (2) case, we have to make it like `convert_var_shape(x)[slice]` if slice_node is not None and slice_is_num(slice_node): args.append(ast_to_source_code(slice_node.slice).strip()) convert_var_shape_func = "paddle.jit.dy2static.convert_var_shape({}, in_control_flow={})".format( ",".join(args), in_control_flow) api_shape_node = gast.parse(convert_var_shape_func).body[0].value if slice_node is not None and not slice_is_num(slice_node): return gast.Subscript( value=api_shape_node, slice=slice_node.slice, ctx=gast.Load()) return api_shape_node if isinstance(var_shape_node, gast.Subscript): result_node = copy.deepcopy(var_shape_node) result_node = create_convert_shape_node(result_node.value, result_node, in_control_flow) return result_node
def _replace_pop(self, node): """ Replace a pop statement for a list or dict. For example: list_a = [0,1,2,3,4] x = list_a.pop() # --> convert_pop(list_a) y = list_a.pop(1) # --> convert_pop(list_a, 1) dict_a = {"red":0, "blue":1, "yellow":2} m = dict_a.pop("red") # --> convert_pop(dict_a, "red") n = dict_a.pop("black", 3) # --> convert_pop(dict_a, "black", 3) """ assert isinstance(node, gast.Call) assert isinstance(node.func, gast.Attribute) target_node = node.func.value target_str = ast_to_source_code(target_node).strip() args_str = [ast_to_source_code(arg).strip() for arg in node.args] # NOTE(liym27): # 1. pop stmt for a list if len(args_str) == 0 # 2. pop stmt for a list or dict if len(args_str) == 1 # 3. pop stmt for a dict if len(args_str) == 2 if len(args_str) <= 2: new_pop_str = "paddle.jit.dy2static.convert_pop({}, {})"\ .format(target_str, ",".join(args_str)) new_pop_node = gast.parse(new_pop_str).body[0].value return new_pop_node else: return node
def visit_Assert(self, node): convert_assert_node = gast.parse( 'fluid.dygraph.dygraph_to_static.convert_operators.convert_assert({test}, {msg})' .format(test=ast_to_source_code(node.test), msg=ast_to_source_code(node.msg) if node.msg else "")).body[0].value return gast.Expr(value=convert_assert_node)
def visit_Assert(self, node): convert_assert_node = gast.parse( 'paddle.jit.dy2static.convert_assert({test}, {msg})'.format( test=ast_to_source_code(node.test), msg=ast_to_source_code(node.msg) if node.msg else "")).body[0].value return gast.Expr(value=convert_assert_node)
def visit_Call(self, node): # Store type var names such as "isinstance(x, some_type_names)" and # Remove them later if isinstance(node.func, gast.Name) and node.func.id == 'isinstance': type_node = node.args[1] if isinstance(type_node, gast.Tuple): for element in type_node.elts: self.type_vars.add(ast_to_source_code(element).strip()) else: self.type_vars.add(ast_to_source_code(type_node).strip()) self.generic_visit(node)
def visit_Call(self, node): self.generic_visit(node) func_str = ast_to_source_code(node.func).strip() if func_str in self._castable_type and len(node.args) > 0: args_str = ast_to_source_code(node.args[0]).strip() new_func_str = "fluid.dygraph.dygraph_to_static.convert_operators.convert_var_dtype({}, '{}')".format( args_str, func_str) new_node = gast.parse(new_func_str).body[0].value return new_node return node
def _need_transform(self, var_node, print_node): if isinstance(var_node, gast.Name): if self._is_tensor_node(var_node): return True else: _logger.warning( "ProgramTranslator could not transform printing value that are not Tensor like < %s > now and will run it as-is." % ast_to_source_code(print_node).strip()) else: _logger.warning( "ProgramTranslator could not transform < %s > now and will run it as-is." % ast_to_source_code(print_node).strip()) return False
def _update_name_to_var_shape(self, node): assert isinstance(node, gast.Assign) target_node = node.targets[0] value_node = node.value if isinstance(target_node, gast.Tuple): has_updated = False for idx, element in enumerate(target_node.elts): target_id = ast_to_source_code(element).strip() if isinstance(value_node, gast.Name): if value_node.id in self.name_to_var_shape: index_value_node = gast.Constant(value=idx, kind=None) slice_index_node = gast.Index(value=index_value_node) var_shape_node = self.name_to_var_shape[value_node.id] sub_node = gast.Subscript(value=var_shape_node, slice=slice_index_node, ctx=gast.Load()) self.name_to_var_shape[target_id] = sub_node has_updated = True if isinstance(value_node, gast.Attribute): if self.is_var_shape(value_node): # eg: x.shape index_value_node = gast.Constant(value=idx, kind=None) slice_index_node = gast.Index(value=index_value_node) sub_node = gast.Subscript(value=value_node, slice=slice_index_node, ctx=gast.Load()) self.name_to_var_shape[target_id] = sub_node has_updated = True return has_updated else: target_id = ast_to_source_code(target_node).strip() if isinstance(value_node, gast.Name): if value_node.id in self.name_to_var_shape: self.name_to_var_shape[target_id] = self.name_to_var_shape[ value_node.id] return True if isinstance(value_node, gast.Attribute): if self.is_var_shape(value_node): # eg: x.shape self.name_to_var_shape[target_id] = value_node return True if isinstance(value_node, gast.Subscript): if isinstance(value_node.value, gast.Attribute): if self.is_var_shape(value_node.value): # eg: x.shape[0] self.name_to_var_shape[target_id] = value_node return True return False
def transfer_from_node_type(self, node_wrapper): self.translator_logger.log( 1, "Source code: \n{}".format(ast_to_source_code(self.root))) # Generic transformation self.visit(node_wrapper.node) transformers = [ BasicApiTransformer, # Basic Api TensorShapeTransformer, # Tensor.shape -> layers.shape(Tensor) ListTransformer, # List used in control flow BreakTransformOptimizer, # optimize transfromation of break in loops BreakContinueTransformer, # break/continue in loops ReturnTransformer, # return in functions LogicalTransformer, # logical and/or/not LoopTransformer, # for/while -> while_op IfElseTransformer, # if/else -> cond_op AssertTransformer, # assert statement PrintTransformer, # print statement CallTransformer, # transform call recursively CastTransformer, # type casting statement GradTransformer, # transform paddle.grad to paddle.gradients ] for index, transformer in enumerate(transformers): self._apply(transformer, node_wrapper, log_level=index + 1) self.translator_logger.log_transformed_code( logging_utils.LOG_AllTransformer, self.root, "All Transformers")
def _create_bool_op_node(self, nodes, api_type): ''' NOTE(liym27): The arguments of function convert_logical_XX should be callable so that they can be run according to the actual order. In `convert_logical_and(lambda:x>1, lambda:y<1)`, `lambda:y<1` must be run after `lambda:x>1`, If `x>1` is False, `y<1` should NOT be run. ''' assert len( nodes ) > 1, "The length of BoolOp should be at least 2, but received {}.".format( len(nodes)) if len(nodes) > 2: # Creates logic_and/logic_or node recursively. pre_logic_node = self._create_bool_op_node(nodes[:2], api_type) if len(nodes[2:]) == 1: post_logic_node = nodes[2] else: post_logic_node = self._create_bool_op_node( nodes[2:], api_type) nodes = [pre_logic_node] + [post_logic_node] args = [ast_to_source_code(child) for child in nodes] new_node_str = "paddle.jit.dy2static.convert_logical_{}(lambda:{}, lambda:{})".format( api_type, args[0], args[1]) # NOTE: gast.parse return Module(body=[expr(...)]) new_node = gast.parse(new_node_str).body[0].value return new_node
def _replace_after_node_to_if_in_stmt_list(self, stmt_list, node, return_name, parent_node_of_return): i = index_in_list(stmt_list, node) if i < 0 or i >= len(stmt_list): return False if i == len(stmt_list) - 1: # No need to add, we consider this as added successfully return True if_stmt = gast.If(test=gast.UnaryOp(op=gast.Not(), operand=gast.Name( id=return_name, ctx=gast.Store(), annotation=None, type_comment=None)), body=stmt_list[i + 1:], orelse=[]) stmt_list[i + 1:] = [if_stmt] # Here assume that the parent node of return is gast.If if isinstance(parent_node_of_return, gast.If): # Prepend control flow boolean nodes such as '__return@1 = False' node_str = "{} = _jst.create_bool_as_type({}, False)".format( return_name, ast_to_source_code(parent_node_of_return.test).strip()) assign_false_node = gast.parse(node_str).body[0] stmt_list[i:i] = [assign_false_node] return True
def _create_tensor_array(self, value_node): # Although `dtype='float32'`, other types such as `int32` can also be supported init_value = ast_to_source_code(value_node).strip() func_code = "paddle.tensor.create_array('float32', {})".format( init_value) func_node = gast.parse(func_code).body[0].value return func_node
def _replace_list_pop(self, node): assert isinstance(node, gast.Call) assert isinstance(node.func, gast.Attribute) target_node = node.func.value target_str = ast_to_source_code(target_node).strip() if node.args: idx_node = node.args[0] idx_str = ast_to_source_code(idx_node).strip() else: idx_str = "None" new_call_str = "fluid.dygraph.dygraph_to_static.list_transformer.convert_list_pop({}, {})".format( target_str, idx_str) new_call_node = gast.parse(new_call_str).body[0].value return new_call_node
def _transform_slice_to_tensor_write(self, node): assert isinstance(node, gast.Assign) target_node = node.targets[0] target_name = target_node.value.id slice_node = target_node.slice if isinstance(slice_node, gast.Slice): pass elif isinstance(slice_node, gast.Index): value_code = ast_to_source_code(node.value) i = "paddle.cast(" \ "x=paddle.jit.dy2static.to_static_variable({})," \ "dtype='int64')".format(ast_to_source_code(slice_node)) assign_code = "{} = fluid.layers.array_write(x={}, i={}, array={})" \ .format(target_name, value_code, i, target_name) assign_node = gast.parse(assign_code).body[0] return assign_node
def _is_builtin_call(self, node): assert isinstance(node, gast.Call) func_str = ast_to_source_code(node.func).strip() try: from paddle.fluid.dygraph.dygraph_to_static.convert_call_func import is_builtin return eval("is_builtin({})".format(func_str)) except Exception: return False
def visit_Attribute(self, node): if node.attr == 'shape': args = ast_to_source_code(node.value).strip() convert_var_shape_func = "paddle.jit.dy2static.convert_var_shape_simple({})".format( args) api_shape_node = gast.parse(convert_var_shape_func).body[0].value return api_shape_node return node
def log_transformed_code(self, level, ast_node, transformer_name, *args, **kwargs): if self.has_code_level(level): source_code = ast_to_source_code(ast_node) header_msg = "After the level {} ast transformer: '{}', the transformed code:\n"\ .format(level, transformer_name) msg = header_msg + source_code self.logger.info(msg, *args, **kwargs)
def visit_UnaryOp(self, node): self.generic_visit(node) if isinstance(node.op, gast.Not): arg = ast_to_source_code(node.operand) new_node_str = "fluid.layers.logical_not({})".format(arg) # gast.parse returns Module(body=[expr(value=...)]) new_node = gast.parse(new_node_str).body[0].value return new_node return node
def visit_Attribute(self, node): if self._used_by_paddle_api(node): name = ast_to_source_code(node).strip() if name in self.name_to_var_shape: return create_choose_shape_node(name, self.name_to_var_shape[name]) if self._is_var_shape(node): return create_convert_shape_node(node) return node
def is_candidate_node(node): """ Nodes with specified type will be dependent on tensor. """ is_compare_node = isinstance(node, (gast.Compare, gast.BoolOp, gast.UnaryOp)) # TODO(Aurelius84): `.numpy()` may be an customized function, # and should consider a more elegant way to solve this problem. has_numpy_attr = ".numpy()" in ast_to_source_code(node) return is_compare_node or has_numpy_attr
def visit_UnaryOp(self, node): self.generic_visit(node) if isinstance(node.op, gast.Not): arg = ast_to_source_code(node.operand) new_node_str = "paddle.jit.dy2static.convert_logical_not({})".format( arg) # NOTE: gast.parse returns Module(body=[expr(value=...)]) new_node = gast.parse(new_node_str).body[0].value return new_node return node
def is_to_variable(node): assert isinstance(node, gast.Call) api_name = utils.ast_to_source_code(node.func).strip() if utils.is_dygraph_api(node): return api_name.endswith("to_variable") if utils.is_paddle_api(node): return api_name.endswith("to_tensor") return False
def is_grad_api_node(node): assert isinstance(node, gast.Call) api_name = utils.ast_to_source_code(node.func).strip() if utils.is_paddle_api(node): if 'no_grad' in api_name: warnings.warn( "paddle.no_grad is only supported for inference model, and not supported for training under @to_static." ) return False return api_name.endswith("grad") return False
def test_create_fill_constant_node(self): node = create_fill_constant_node("a", 1.0) source = "a = paddle.fluid.layers.fill_constant(shape=[1], dtype='float64', value=1.0, name='a')" self.assertEqual( ast_to_source_code(node).replace('\n', '').replace(' ', ''), source.replace(' ', '')) node = create_fill_constant_node("b", True) source = "b = paddle.fluid.layers.fill_constant(shape=[1], dtype='bool', value=True, name='b')" self.assertEqual( ast_to_source_code(node).replace('\n', '').replace(' ', ''), source.replace(' ', '')) node = create_fill_constant_node("c", 4293) source = "c = paddle.fluid.layers.fill_constant(shape=[1], dtype='int64', value=4293, name='c')" self.assertEqual( ast_to_source_code(node).replace('\n', '').replace(' ', ''), source.replace(' ', '')) self.assertIsNone(create_fill_constant_node("e", None)) self.assertIsNone(create_fill_constant_node("e", []))
def _create_node_with_api_template(self, node, template): node_code = ast_to_source_code(node) new_node_str = template.format(node_code) # gast.parse return Module(body=[expr(value=...)]) new_node = gast.parse(new_node_str).body[0].value bool_tensor_name = unique_name.generate(PLAIN_TENSOR_PREFIX) assign_name, assign_node = create_assign_node(bool_tensor_name, new_node) self._new_assign_nodes.append(assign_node) return assign_name
def visit_Compare(self, node): self.generic_visit(node) left_str = ast_to_source_code(node.left).strip() if left_str.startswith("_jst.convert_var_shape"): # check left and comparators are all converted var shape compare_arg_strs = left_str for i, comparator in enumerate(node.comparators): comparator_str = ast_to_source_code(comparator).strip() if not comparator_str.startswith("_jst.convert_var_shape"): return node op_str = cmpop_node_to_str(node.ops[i]) compare_arg_strs += (", '" + op_str + "', " + comparator_str) # Now all left and comparators are converted shape # Replace some comparsion operation because of difference between # Python and Paddle new_node_str = "_jst.convert_shape_compare({})".format( compare_arg_strs) new_node = gast.parse(new_node_str).body[0].value return new_node return node
def _create_bool_node(self, node): node_code = ast_to_source_code(node) new_node_str = "fluid.layers.fill_constant(shape=[1], dtype='bool', value=bool({}))".format( node_code) # gast.parse return Module(body=[expr(value=...)]) new_node = gast.parse(new_node_str).body[0].value bool_tensor_name = unique_name.generate(PLAIN_TENSOR_PREFIX) assign_name, assign_node = create_assign_node(bool_tensor_name, new_node) self._new_assign_nodes.append(assign_node) return assign_name
def create_choose_shape_node(attr_shape_name, api_shape_name, slice_node=None): eval_exist_func = "paddle.jit.dy2static.eval_if_exist_else_none('{}', globals())".format( api_shape_name) args = [attr_shape_name, eval_exist_func] if slice_node is not None and slice_is_num(slice_node): args.append(ast_to_source_code(slice_node.slice).strip()) choose_shape_func = "paddle.jit.dy2static.choose_shape_attr_or_api({})".format( ",".join(args)) choose_shape_node = gast.parse(choose_shape_func).body[0].value if slice_node is not None and not slice_is_num(slice_node): return gast.Subscript( value=choose_shape_node, slice=slice_node.slice, ctx=gast.Load()) return choose_shape_node
def visit_UnaryOp(self, node): self.generic_visit(node) if isinstance(node.op, gast.Not): arg = ast_to_source_code(node.operand) new_node_str = "fluid.layers.logical_not({})".format(arg) # gast.parse returns Module(body=[expr(value=...)]) new_node = gast.parse(new_node_str).body[0].value logic_tensor_name = unique_name.generate(LOGIC_NOT_PREFIX) assign_name, assign_node = create_assign_node( logic_tensor_name, new_node) self._new_assign_nodes.append(assign_node) return assign_name return node
def _need_to_array_write_node(self, node): if isinstance(node, gast.Expr): if isinstance(node.value, gast.Call): if self._is_list_append_tensor(node.value): return True if isinstance(node, gast.Assign): target_node = node.targets[0] if isinstance(target_node, gast.Subscript): list_name = ast_to_source_code(target_node.value).strip() if list_name in self.list_name_to_updated: if self.list_name_to_updated[list_name] == True: return True return False