Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New destroy handler #6000

Merged
merged 34 commits into from Aug 14, 2017
Merged

Conversation

ReyhaneAskari
Copy link
Member

@ReyhaneAskari ReyhaneAskari commented Jun 1, 2017

Issue #5976. Supervisor class is kept but we added a has_destroyers attribute to fgraph, but some tests regarding dumping the pickle failed because a new attribute was added to the fgraph. I changed it like this :

fgraph.destroyers = [get_destroyers_of, has_destroyers]

@nouiz what do you suggest?

fix #6230

@lamblin
Copy link
Member

lamblin commented Jun 1, 2017

Now that #5794 has been merged, you will probably need to rebase on the master. That will make the diff easier to check.

for item in l:
droot, _, root_destroyer = self.refresh_droot_impact()
try:
[root_destroyer[droot[item]]]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you wanted this line to be:

if root_destroyer[droot[item]]:
     return True

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah! thanks.

try:
[root_destroyer[droot[item]]]
return True
except Exception:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know you copied the Exception from above, but this is a bad practice. We should try to catch a much more precise exception.

Anyway, this isn't the optimized algorythm anyway. Both otherwise, you seem to have done the first part well. If with my modif in the other comment and tests pass, the structural change would be done. It would only miss the optimized implementation here.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are right! Thanks I changed it to KeyError

for r in self.protected + list(fgraph.outputs):
if fgraph.destroyers(r):
if config.cycle_detection == 'fast':
if fgraph.destroyers[1](self.protected + list(fgraph.outputs)):
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@nouiz , do we need to pass fgraph.outputs here ?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think so.

if fgraph.fast_destroyers_check(self.protected):
raise gof.InconsistencyError("Trying to destroy a protected"
"Variable.")

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

else: return True

def recursive_destroys_finder(clients_list):
for client in clients_list:
# client is a tuple (I don't know if its size is always one)
for item in client:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it would be more like:

        def recursive_destroys_finder(clients_list):
                for (app, idx) in clients_list:
                    if app == 'output':
                        continue
                    if idx in flatten(getattr(app.op, 'destroy_map', {}).values()):
                        return True
                    for var in app.outputs[getattr(app.op, 'view_map', {}).keys()]:
                        if recursive_destroys_finder(var.clients):
                            return True
                return False

for protected_var in protected_list:
if recursive_destroys_finder(protected_var.clients):
clients_set.update(protected_var.clients)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the clients list of tuple (app, idx) is already a set.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So mostly, that commit don't help. There is some saving possible if one node reuse the same variable multiple time as input, but it happen so infrequently that I don't think the extra complexity is useful.

@ReyhaneAskari
Copy link
Member Author

I removed that commit. Now we need to check the failing tests and make sure they are the same tests that are failing in the master with THEANO_FLAGS=cycle_detetion='fast'. @lamblin suggested that I mark the failing tests in master as nosetests' known failures.

@nouiz
Copy link
Member

nouiz commented Jun 5, 2017 via email

def has_destroyers(protected_list):
visited_app_set = set()

def recursive_destroys_finder(clients_list):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would change the interface to have this function take a variable as input. Then it would loop over var.clients itself. I think it is a nicer interface and will help the conversion to the recursion free version.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure. Thanks.

destroy_maps = getattr(app.op, 'destroy_map', {}).values()
if idx in [dmap for sublist in destroy_maps for dmap in sublist]:
return True
for var in getattr(app.op, 'view_map', {}).keys():
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here, this is too much restrictif. We are asking that all view of app are not destroyed. But we only care the outputs that are a view of app.inputs[idx].

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see. Thanks. Is this check good?

for var in getattr(app.op, 'view_map', {}).keys():
    if idx in app.op.view_map[var] and recursive_destroys_finder(app.outputs[var]):
          return True

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

that seem good. To help read it, I would rename var to var_idx, to tell it is an index, not a variable.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure. Thanks.

@ReyhaneAskari
Copy link
Member Author

@lamblin, @nouiz I could only find unittest.expectedFailure as a decorator. To make sure it only adds when cycle_detection='fast', I wrote another decorator like this because there were no builtin options:

import unittest
from theano import config

def expectedFailure_fast():
    return unittest.expectedFailure if config.cycle_detection == 'fast' else lambda x: x

@expectedFailure_fast()
def test_usage_loop_through_views_2():

Do you have any suggestions on where to add this code snippet? I was thinking maybe theano.gof.utils?

@nouiz
Copy link
Member

nouiz commented Jun 6, 2017

theano.gof.utils seem the right place for that.

if recursive_destroys_finder(app.outputs[var].clients):
return True
if idx in app.op.view_map[var] and recursive_destroys_finder(app.outputs[var]):
return True
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you added an extra indentation that isn't useful.

@nouiz
Copy link
Member

nouiz commented Jun 6, 2017

theano.tests.unittests_tools would be better then theano.gof.utils I think.

@lamblin
Copy link
Member

lamblin commented Jun 7, 2017

Most issues seem to be:

 Error Details

OutputGuard.0 is a view/destroyed version of more then one inputs. Currently, we only support the case where an output is a view or a destroyed version of one input.

 Stack Trace

Traceback (most recent call last):
  File "/miniconda/lib/python2.7/unittest/case.py", line 329, in run
    testMethod()
  File "/home/jenkins/workspace/Theano_PR/theano/typed_list/tests/test_basic.py", line 257, in test_inplace
    accept_inplace=True)
  File "/home/jenkins/workspace/Theano_PR/theano/compile/function.py", line 326, in function
    output_keys=output_keys)
  File "/home/jenkins/workspace/Theano_PR/theano/compile/pfunc.py", line 486, in pfunc
    output_keys=output_keys)
  File "/home/jenkins/workspace/Theano_PR/theano/compile/function_module.py", line 1814, in orig_function
    output_keys=output_keys)
  File "/home/jenkins/workspace/Theano_PR/theano/compile/function_module.py", line 1491, in __init__
    insert_deepcopy(fgraph, inputs, outputs + additional_outputs)
  File "/home/jenkins/workspace/Theano_PR/theano/compile/function_module.py", line 1099, in insert_deepcopy
    view_tree_set(alias_root(fgraph.outputs[i]), views_of_output_i)
  File "/home/jenkins/workspace/Theano_PR/theano/compile/function_module.py", line 57, in alias_root
    str(v) + " is a view/destroyed version of more then one inputs. "
NotImplementedError: OutputGuard.0 is a view/destroyed version of more then one inputs. Currently, we only support the case where an output is a view or a destroyed version of one input.

@@ -27,6 +27,7 @@ def makeSharedTester(shared_constructor_,
theano_fct_,
ref_fct_,
cast_value_=np.asarray,
need_decorator=True,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't forget @lamblin comment in gh-6014.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I'm on it.

@ReyhaneAskari
Copy link
Member Author

ReyhaneAskari commented Jun 8, 2017

There was a small conflict here with jenkins and travis wouldn't run with out resolving it. I resolved the conflict online and committed it. The name was automatically generated as Merge branch 'master' into new_destroy_handler. I will have to rebase in the end and I will clean this up.

@ReyhaneAskari ReyhaneAskari force-pushed the new_destroy_handler branch 2 times, most recently from 1cc937e to 5c79a2f Compare June 8, 2017 19:40
@ReyhaneAskari
Copy link
Member Author

@lamblin @nouiz
There were so many tests that were failing due to the insertion of OutputGurad. I checked several tests and I figured that we should raise an error when AdddestoryHandler tries to insert the OutputGurad . So we need to check the graph.outputs as well as the protected_var. I also changed the has_destroyer such that in the case when the protected_var is an outputGuard, we do the check.

I was not sure if there are cases where app == 'output' but the protected_var is not an OutputGuard. There are still some failing tests that I'm looking into.

@ReyhaneAskari ReyhaneAskari force-pushed the new_destroy_handler branch 2 times, most recently from f072886 to 7e4a569 Compare June 9, 2017 21:53
@ReyhaneAskari
Copy link
Member Author

ReyhaneAskari commented Jun 12, 2017

There were many failures in the tests because we were inplace in some cases where we shouldn't have been. I checked these tests and found out that we are not raising the error in the Supervisor class in some cases that the inplace was wrong. The problem lied in the case where we had a graph in which one node was cached in the visited_app_set and never checked again.

The mechanism of visited_app_set is wrong. Consider the following case when an apply node is the client of serval variable nodes and has a destroy map of {0 : [1]}. In the first visit to this node since the destroy map is on the second input, we do not raise any error and we mark the apply node as visited while it should be checked for the second variable node as well where it is destroying one of its inputs.

photo_2017-06-12_13-55-52

I guess it will be too complicated to support these cases. For now I removed the visited_app_set to see if all the tests pass. Then in the benchmarking we can figure where is the bottleneck.

@lamblin
Copy link
Member

lamblin commented Jun 12, 2017

Conclusion of IRL discussion:

  • remove view_map from OutputGuard
  • In AddDestroyHandler, do not try to introduce OutputGuard when fast cycle detection is on
  • undo the new check (full=True).

@ReyhaneAskari
Copy link
Member Author

We won't remove the view_map for the output Guard for now since it's causing many errors. We will handle that in another PR where we'll completely remove the output Guard.

@ReyhaneAskari
Copy link
Member Author

Is there a way to add the flag to the examples inside the docs? Maybe the easier way to change the cycle_detection flag was to change the default value to fast for the testing purposes and revert it back in the end.

@nouiz
Copy link
Member

nouiz commented Jun 13, 2017 via email

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Add doc for fast cycle detection
5 participants