def test_skiphole(self): x,y,z = inputs() a = add(x,y) r = raise_err(a) e = add(r,a) fn = perform_linker(Env(*graph.clone([x, y,r], [e]))).make_function() assert fn(1.0,2.0,4.5) == 7.5
def test_input_dependency0(self): x, y, z = inputs() a, d = add(x, y), div(x, y) e = mul(a, d) fn = perform_linker(FunctionGraph(*graph.clone([x, y, a], [e]))).make_function() assert fn(1.0, 2.0, 9.0) == 4.5
def test_skiphole(self): x, y, z = inputs() a = add(x, y) r = raise_err(a) e = add(r, a) fn = perform_linker(FunctionGraph(*graph.clone([x, y, r], [e]))).make_function() assert fn(1.0, 2.0, 4.5) == 7.5
def test_input_dependency0(self): x, y, z = inputs() a, d = add(x, y), div(x, y) e = mul(a, d) fn = perform_linker( FunctionGraph(*graph.clone([x, y, a], [e]))).make_function() assert fn(1.0, 2.0, 9.0) == 4.5
def __init__(self, inputs, outputs, features=None, clone=True): """ Create an FunctionGraph which operates on the subgraph bound by the inputs and outputs sets. This class keeps a pointer to the inputs and outputs, and also modifies them. #TODO: document what variables are[not] set in the FunctionGraph when a feature is added via the constructor. How constructed is the FunctionGraph? :param clone: If true, we will clone the graph. This is useful to remove the constant cache problem. """ if clone: inputs, outputs = graph.clone(inputs, outputs) self.execute_callbacks_time = 0 self.execute_callbacks_times = {} if features is None: features = [] # XXX: Unless I'm missing something (but there's no documentation, # so I probably am) this should be a set. self._features = [] # All apply nodes in the subgraph defined by inputs and # outputs are cached in this field self.apply_nodes = set() # Ditto for variable nodes self.variables = set() self.inputs = list(inputs) self.outputs = outputs for f in features: self.attach_feature(f) self.attach_feature(toolbox.ReplaceValidate()) for input in self.inputs: if input.owner is not None: raise ValueError("One of the provided inputs is the output of" "an already existing node. " "If that is okay, either discard that " "input's owner or use graph.clone.") self.__setup_r__(input) self.variables.add(input) self.__import_r__(outputs, reason="init") for i, output in enumerate(outputs): output.clients.append(('output', i)) self.node_locks = {} self.variable_locks = {} self.profile = None
def test_not_destructive(self): # Checks that manipulating a cloned graph leaves the original unchanged. r1, r2, r5 = MyVariable(1), MyVariable(2), MyVariable(5) node = MyOp.make_node(MyOp.make_node(r1, r2).outputs[0], r5) _, new = clone([r1, r2, r5], node.outputs, False) new_node = new[0].owner new_node.inputs = MyVariable(7), MyVariable(8) assert self.str(inputs(new_node.outputs), new_node.outputs) == ["MyOp(R7, R8)"] assert self.str(inputs(node.outputs), node.outputs) == ["MyOp(MyOp(R1, R2), R5)"]
def test_copy(self): r1, r2, r5 = MyVariable(1), MyVariable(2), MyVariable(5) node = MyOp.make_node(r1, r2) node2 = MyOp.make_node(node.outputs[0], r5) _, new = clone([r1, r2, r5], node2.outputs, False) assert node2.outputs[0].type == new[0].type and node2.outputs[0] is not new[0] # the new output is like the old one but not the same object assert node2 is not new[0].owner # the new output has a new owner assert new[0].owner.inputs[1] is r5 # the inputs are not copied assert new[0].owner.inputs[0].type == node.outputs[0].type and new[0].owner.inputs[0] is not node.outputs[0] # check that we copied deeper too
def test_constant(self): r1, r2, r5 = MyVariable(1), MyVariable(2), MyVariable(5) node = MyOp.make_node(MyOp.make_node(r1, r2).outputs[0], r5) _, new = clone([r1, r2, r5], node.outputs, False) new_node = new[0].owner new_node.inputs = MyVariable(7), MyVariable(8) c1 = tensor.constant(1.5) i, o = clone([c1], [c1]) assert i[0] is not c1 and o[0] is not c1 i, o = clone([c1], [c1], False) assert i[0] is c1 and o[0] is c1 i, o = clone([c1], [c1], True, False) assert i[0] is not c1 and o[0] is not c1 i, o = clone([c1], [c1], False, True) assert i[0] is c1 and o[0] is c1
def test_constant(self): r1, r2, r5 = MyVariable(1), MyVariable(2), MyVariable(5) node = MyOp.make_node(MyOp.make_node(r1, r2).outputs[0], r5) _, new = clone([r1, r2, r5], node.outputs, False) new_node = new[0].owner new_node.inputs = [MyVariable(7), MyVariable(8)] c1 = tensor.constant(1.5) i, o = clone([c1], [c1]) assert i[0] is not c1 and o[0] is not c1 i, o = clone([c1], [c1], False) assert i[0] is c1 and o[0] is c1 i, o = clone([c1], [c1], True, False) assert i[0] is not c1 and o[0] is not c1 i, o = clone([c1], [c1], False, True) assert i[0] is c1 and o[0] is c1
def test_long_destroyers_loop(): x, y, z = inputs() e = dot(dot(add_in_place(x,y), add_in_place(y,z)), add(z,x)) g = Env([x,y,z], [e]) consistent(g) OpSubOptimizer(add, add_in_place).optimize(g) consistent(g) assert str(g) != "[Dot(Dot(AddInPlace(x, y), AddInPlace(y, z)), AddInPlace(z, x))]" # we don't want to see that! e2 = dot(dot(add_in_place(x,y), add_in_place(y,z)), add_in_place(z,x)) try: g2 = Env(*graph.clone([x,y,z], [e2])) raise Exception("Shouldn't have reached this point.") except InconsistencyError: pass
def test_long_destroyers_loop(): x, y, z = inputs() e = dot(dot(add_in_place(x, y), add_in_place(y, z)), add(z, x)) g = Env([x, y, z], [e]) assert g.consistent() OpSubOptimizer(add, add_in_place).optimize(g) assert g.consistent() # we don't want to see that! assert str( g ) != "[Dot(Dot(AddInPlace(x, y), AddInPlace(y, z)), AddInPlace(z, x))]" e2 = dot(dot(add_in_place(x, y), add_in_place(y, z)), add_in_place(z, x)) with pytest.raises(InconsistencyError): Env(*graph.clone([x, y, z], [e2]))
def __init__(self, inputs, outputs, features=None, clone=True): if clone: inputs, outputs = graph.clone(inputs, outputs) self.execute_callbacks_time = 0 self.execute_callbacks_times = {} if features is None: features = [] # XXX: Unless I'm missing something (but there's no documentation, # so I probably am) this should be a set. self._features = [] # All apply nodes in the subgraph defined by inputs and # outputs are cached in this field self.apply_nodes = set() # Ditto for variable nodes. # It must contain all fgraph.inputs and all apply_nodes # outputs even if they aren't used in the graph. self.variables = set() self.inputs = list(inputs) self.outputs = outputs for f in features: self.attach_feature(f) self.attach_feature(toolbox.ReplaceValidate()) for input in self.inputs: if input.owner is not None: raise ValueError("One of the provided inputs is the output of" "an already existing node. " "If that is okay, either discard that " "input's owner or use graph.clone.") self.__setup_r__(input) self.variables.add(input) for output in outputs: self.__import_r__(output, reason="init") for i, output in enumerate(outputs): output.clients.append(('output', i)) self.node_locks = {} self.variable_locks = {} self.profile = None
def test_accurate(self): r1, r2 = MyVariable(1), MyVariable(2) node = MyOp.make_node(r1, r2) _, new = clone([r1, r2], node.outputs, False) assert self.str([r1, r2], new) == ["MyOp(R1, R2)"]
def __init__(self, inputs, outputs, features=None, clone=True, update_mapping=None): """ Create an FunctionGraph which operates on the subgraph bound by the inputs and outputs sets. Parameters ---------- inputs : list of variables Inputs nodes of the graph, usually declared by the user outputs : list of variables Outputs nodes of the graph. clone : boolean If true, we will clone the graph. This is useful to remove the constant cache problem. update_mapping : dictionary Mapping between the inputs with updates and the outputs corresponding to their updates. """ if clone: inputs, outputs = graph.clone(inputs, outputs) self.execute_callbacks_time = 0 self.execute_callbacks_times = {} if features is None: features = [] # XXX: Unless I'm missing something (but there's no documentation, # so I probably am) this should be a set. self._features = [] # All apply nodes in the subgraph defined by inputs and # outputs are cached in this field self.apply_nodes = set() # Ditto for variable nodes. # It must contain all fgraph.inputs and all apply_nodes # outputs even if they aren't used in the graph. self.variables = set() self.inputs = list(inputs) self.outputs = outputs for f in features: self.attach_feature(f) self.attach_feature(toolbox.ReplaceValidate()) for input in self.inputs: if input.owner is not None: raise ValueError("One of the provided inputs is the output of" "an already existing node. " "If that is okay, either discard that " "input's owner or use graph.clone.") self.__setup_r__(input) self.variables.add(input) for output in outputs: self.__import_r__(output, reason="init") for i, output in enumerate(outputs): output.clients.append(("output", i)) self.profile = None self.update_mapping = update_mapping
def make_thunk(self, node, storage_map, compute_map, no_recycling): """ :param node: something previously returned by self.make_node :param storage_map: dict variable -> one-element-list where a computed value for this variable may be found. :param compute_map: dict variable -> one-element-list where a boolean value will be found. The boolean indicates whether the variable's storage_map container contains a valid value (True) or if it has not been computed yet (False). :param no_recycling: list of variables for which it is forbidden to reuse memory allocated by a previous call. :note: If the thunk consults the storage_map on every call, it is safe for it to ignore the no_recycling argument, because elements of the no_recycling list will have a value of None in the storage map. If the thunk can potentially cache return values (like CLinker does), then it must not do so for variables in the no_recycling list. """ logger = logging.getLogger('theano.gof.op.Op') node_input_storage = [storage_map[r] for r in node.inputs] node_output_storage = [storage_map[r] for r in node.outputs] node_input_compute = [compute_map[r] for r in node.inputs] node_output_compute = [compute_map[r] for r in node.outputs] #logger.debug('Compiling node %i of graph' % node_idx) if self._op_use_c_code: try: e = FunctionGraph(*graph.clone(node.inputs, node.outputs)) e_no_recycling = [new_o for (new_o, old_o) in zip(e.outputs, node.outputs) if old_o in no_recycling] cl = theano.gof.cc.CLinker().accept(e, no_recycling=e_no_recycling) logger.debug('Trying CLinker.make_thunk') outputs = cl.make_thunk(input_storage=node_input_storage, output_storage=node_output_storage) fill_storage, node_input_filters, node_output_filters = outputs def rval(): fill_storage() for o in node.outputs: compute_map[o][0] = True rval.cthunk = fill_storage.cthunk rval.inputs = node_input_storage rval.outputs = node_output_storage rval.lazy = False return rval # the next line does nothing, but pyflakes is too # stupid to realize the def rval below is not a # redefinition unless I include this del rval except (NotImplementedError, utils.MethodNotDefined): logger.debug('Falling back on perform') # condition: either there was no c_code, or it failed p = node.op.perform # default arguments are stored in the closure of `rval` def rval(p=p, i=node_input_storage, o=node_output_storage, n=node): r = p(n, [x[0] for x in i], o) for o in node.outputs: compute_map[o][0] = True return r rval.inputs = node_input_storage rval.outputs = node_output_storage rval.perform = p rval.lazy = False return rval
def __init__(self, inputs, outputs, features=None, clone=True, update_mapping=None): """ Create an FunctionGraph which operates on the subgraph bound by the inputs and outputs sets. Parameters ---------- inputs : list of variables Inputs nodes of the graph, usually declared by the user outputs : list of variables Outputs nodes of the graph. clone : boolean If true, we will clone the graph. This is useful to remove the constant cache problem. update_mapping : dictionnary Mapping between the inputs with updates and the outputs corresponding to their updates. """ if clone: inputs, outputs = graph.clone(inputs, outputs) self.execute_callbacks_time = 0 self.execute_callbacks_times = {} if features is None: features = [] # XXX: Unless I'm missing something (but there's no documentation, # so I probably am) this should be a set. self._features = [] # All apply nodes in the subgraph defined by inputs and # outputs are cached in this field self.apply_nodes = set() # Ditto for variable nodes. # It must contain all fgraph.inputs and all apply_nodes # outputs even if they aren't used in the graph. self.variables = set() self.inputs = list(inputs) self.outputs = outputs for f in features: self.attach_feature(f) self.attach_feature(toolbox.ReplaceValidate()) for input in self.inputs: if input.owner is not None: raise ValueError("One of the provided inputs is the output of" "an already existing node. " "If that is okay, either discard that " "input's owner or use graph.clone.") self.__setup_r__(input) self.variables.add(input) for output in outputs: self.__import_r__(output, reason="init") for i, output in enumerate(outputs): output.clients.append(('output', i)) self.profile = None self.update_mapping = update_mapping
def make_thunk(self, node, storage_map, compute_map, no_recycling): """ :param node: something previously returned by self.make_node :param storage_map: dict variable -> one-element-list where a computed value for this variable may be found. :param compute_map: dict variable -> one-element-list where a boolean value will be found. The boolean indicates whether the variable's storage_map container contains a valid value (True) or if it has not been computed yet (False). :param no_recycling: list of variables for which it is forbidden to reuse memory allocated by a previous call. :note: If the thunk consults the storage_map on every call, it is safe for it to ignore the no_recycling argument, because elements of the no_recycling list will have a value of None in the storage map. If the thunk can potentially cache return values (like CLinker does), then it must not do so for variables in the no_recycling list. """ logger = logging.getLogger('theano.gof.op.Op') node_input_storage = [storage_map[r] for r in node.inputs] node_output_storage = [storage_map[r] for r in node.outputs] node_input_compute = [compute_map[r] for r in node.inputs] node_output_compute = [compute_map[r] for r in node.outputs] #logger.debug('Compiling node %i of graph' % node_idx) if self._op_use_c_code: try: e = FunctionGraph(*graph.clone(node.inputs, node.outputs)) e_no_recycling = [ new_o for (new_o, old_o) in zip(e.outputs, node.outputs) if old_o in no_recycling ] cl = theano.gof.cc.CLinker().accept( e, no_recycling=e_no_recycling) logger.debug('Trying CLinker.make_thunk') outputs = cl.make_thunk(input_storage=node_input_storage, output_storage=node_output_storage) fill_storage, node_input_filters, node_output_filters = outputs def rval(): fill_storage() for o in node.outputs: compute_map[o][0] = True rval.cthunk = fill_storage.cthunk rval.inputs = node_input_storage rval.outputs = node_output_storage rval.lazy = False return rval # the next line does nothing, but pyflakes is too # stupid to realize the def rval below is not a # redefinition unless I include this del rval except (NotImplementedError, utils.MethodNotDefined): logger.debug('Falling back on perform') # condition: either there was no c_code, or it failed p = node.op.perform # default arguments are stored in the closure of `rval` def rval(p=p, i=node_input_storage, o=node_output_storage, n=node): r = p(n, [x[0] for x in i], o) for o in node.outputs: compute_map[o][0] = True return r rval.inputs = node_input_storage rval.outputs = node_output_storage rval.perform = p rval.lazy = False return rval