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

Simulator changes (nengo_deeplearning compatibility) #1245

Merged
merged 6 commits into from
Feb 8, 2017
Merged

Conversation

drasmuss
Copy link
Member

@drasmuss drasmuss commented Dec 16, 2016

Motivation and context:
This is kind of a grab bag of issues I came across while working on updates to nengo_deeplearning. No new features, just fixing some bugs/inconsistencies/unnecessary code. If anyone wants I can split these into separate PRs, but they're all 2--3 line changes so I thought I'd help out our PR queue and keep it all together (at least to start).

How has this been tested?
Added unit tests for the bug fixes (to show that the bug is fixed). For the parts where I took out unnecessary simulator operations, the test is just that all the unit tests continue to pass with that bit removed.

How long should this take to review?

  • Quick (less than 40 lines changed or changes are straightforward)

Where should a reviewer start?
Go through one commit at a time, as each is independent.

Types of changes:

  • Bug fix (non-breaking change which fixes an issue)

Checklist:

  • I have read the CONTRIBUTING.rst document.
  • I have updated the documentation accordingly.
  • [n/a] I have included a changelog entry.
  • I have added tests to cover my changes.
  • All new and existing tests passed.

@drasmuss
Copy link
Member Author

@hunse I noticed something in this breaks nengo_ocl. Here's the error:

File "d:\documents\nengo_ocl\nengo_ocl\simulator.py", line 293, in __init__
    self._plan.extend(self.plan_op_group(op_type, op_list))
  File "d:\documents\nengo_ocl\nengo_ocl\simulator.py", line 562, in plan_op_group
    return getattr(self, 'plan_' + op_type.__name__)(ops)
  File "d:\documents\nengo_ocl\nengo_ocl\simulator.py", line 633, in plan_SlicedCopy
    plans.append(plan_copy(self.queue, A, B, incs))
  File "d:\documents\nengo_ocl\nengo_ocl\clra_nonlinearities.py", line 212, in plan_copy
    assert (arr.shape1s == 1).all()
AssertionError

I believe it is this change 75db8be#diff-d4a1ea7c740725a930af968ec8143735R350 where I changed the target += delta*1 to target += delta. Is the requirement that all copies be on vectors (not matrices) a hard one? If so we can always go back to the delta*1, just thought I'd see if it's an easy fix or not.

@hunse
Copy link
Collaborator

hunse commented Dec 20, 2016

It's not hard to change. One question is how significant the performance drop would be if the data isn't contiguous. I'm also not sure if that performance drop is a run-time thing or compile-time. If it's run-time, then only data that's actually not contiguous would be slower to copy. If it's compile time, then everything could be slower, because the compiler doesn't know if the data's contiguous or not, so it has to assume not.

The simplest would be to make the change and run some large models (with the profiler on) and see if it makes a difference. I have a circular convolution example set up for benchmarking, so testing on that might be enough.

So the short answer is it's doable, though if the change can wait, or someone else wants to do it, then that's better for me.

@drasmuss
Copy link
Member Author

It's not an urgent change at all, since I can just patch the builder within nengo_deeplearning to change the ElementwiseInc to a SlicedCopy just for that backend. We only need to update it (if we do update it) in nengo if we want that efficiency improvement for all nengo builder backends. And, like you say, if it makes the copy operation slower then we probably wouldn't want that in nengo_ocl anyway.

@drasmuss
Copy link
Member Author

I moved that bit into its own PR, so the remaining changes here should all be compatible with nengo_ocl.

@@ -125,30 +124,6 @@ def test_signal_values():
two_d.initial_value[...] = np.array([[0.5], [-0.5]])


def test_signal_init_values(RefSimulator):
Copy link
Contributor

Choose a reason for hiding this comment

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

FYI, for the next reviewer, this test is duplicated specifically by this test.

@Seanny123
Copy link
Contributor

LGTM, but I'm not super confident about the 2nd commit because I have a very limited familiarity with the builder. Hopefully, the next reviewer will assuage my fears.

name of function object
"""

return getattr(func, "__name__", func.__class__.__name__)
Copy link
Collaborator

Choose a reason for hiding this comment

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

I believe at some point all instances of obj.__class__ were changed to type(obj). Would that be appropriate here too? Or is there a reason to prefer obj.__class__?

Copy link
Member Author

@drasmuss drasmuss Dec 21, 2016

Choose a reason for hiding this comment

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

type(obj) was what I had originally, but in Python 2.7 type(func).__name__ just displays 'instance', whereas func.__class__.__name__ gives you the name of the class.

Copy link
Collaborator

Choose a reason for hiding this comment

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

I had assumed they were the same, and if your class inherits from object they are. For old-style classes though, the behaviour is as @drasmuss described.

Maybe I shouldn't have changed x.__class__ to type(x) everywhere...

Copy link
Collaborator

@hunse hunse Dec 21, 2016

Choose a reason for hiding this comment

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

Looking at the commit though, I did it to help with some old types of objects. I wish I could remember what it was supposed to help with, exactly, because I agree, the name seems worse. 76f0a34

Copy link
Collaborator

Choose a reason for hiding this comment

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

Maybe it was for MRO. For instances of old classes, x.__class__.__mro__ does not exist, whereas type(x).__mro__ does. However (and maybe I didn't realize this at the time I made the changes), type(x).__mro__ just gives (instance, object) no matter what the actual inheritance is, so it's not useful. Does anybody know how to get the MRO of an old-style class?

Anyway, x.__class__.__name__ seems generally better to me than type(x).__name__, so I'm fine to keep it here. We should also maybe consider reverting 76f0a34

Copy link
Collaborator

Choose a reason for hiding this comment

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

Somehow, missed the last bunch of comments and somewhat as side comment: I think we should in general use and encourage the usage of new-style classes (i.e. every class should inherit from object).

Copy link
Collaborator

Choose a reason for hiding this comment

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

I agree, and I think most people do. I would prefer not to have a hard rule about that, though, and if we do, we would want to fail right away with an explicit check, not in some random place.

@jgosmann
Copy link
Collaborator

Changes look good to me, except for potentially a very minor comment I made inline and as Sean not being super confident about the PreserveValue commit. It definitely simplifies things a bit (and would marginally speed up things because less operators to iterate over). To me it seems that the no increment without set/update rule is to avoid hard to find bugs?

@drasmuss
Copy link
Member Author

drasmuss commented Dec 21, 2016

I believe that the main motivation for the no increment without set/update rule was that in the original design we didn't have our current system for setting initial values. Signals were just empty containers with no value. So the rule was supposed to check that the signal actually had a value before you tried to increment it. But we've made some changes to the signal system since then, and now all signals start with initial values, so we don't need to be as worried about that. It was a long time ago we designed that part of the builder though, so that's a bit of guess work.

@hunse
Copy link
Collaborator

hunse commented Dec 21, 2016

My impression was always that the set/update rule was for bug catching, but the initialization argument makes sense, too. It could have been a combination of the two. Anyway, I think we're fine without it, now.

This all LGTM

Copy link
Member

@tbekolay tbekolay left a comment

Choose a reason for hiding this comment

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

LGTM! Removing PreserveValue scares me, but let us press on in the name of progress. I added changelog entries and a commit (ea8644f) that fixed some test failures I was getting (see the commit for details).

tbekolay and others added 6 commits February 8, 2017 15:59
Avoids crashing if func is a callable class, and avoids
printing out the whole array if func is an array.
Removed the requirement that signals that are incremented must also
be set/updated, since it doesn't seem necessary (and forces the
awkwardness of PreserveValue).
@tbekolay tbekolay merged commit 0795f55 into master Feb 8, 2017
@tbekolay tbekolay deleted the dl_compat branch February 9, 2017 17:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

Successfully merging this pull request may close these issues.

None yet

5 participants