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

Python wrapper #202

Merged
merged 137 commits into from
Nov 23, 2018
Merged

Python wrapper #202

merged 137 commits into from
Nov 23, 2018

Conversation

braintimeException
Copy link
Contributor

@braintimeException braintimeException commented Aug 9, 2018

The python wrapper or pygenn is mostly ready.

What is supported:

  • Define, build, run and record, all without leaving cozy Python shell.
  • GeNNModel Python helper class is the main proxy for model manipulation and simulation.
  • New models (NeuronModels, PostsynapticModels, WeightUpdateModels, CurrentSourceModels and InitVarSnippets) can be defined directly in Python. There are also createCustom*Class functions to simplify this process.
  • Add populations and current sources using addNeuronPopulation, addSynapsePopulation and addCurrentSource functions which closely resemble their GeNN counterparts.
  • After the model is defined in can be built and loaded using convenience methods build and load in GeNNModel.
  • Input / output works via numpy array views which works as a pointer (or IS a pointer as far as I understand) to the underlying C-array. If you want to record, simply copy the contents of such array view elsewhere.
  • Can run in both GPU and CPU modes

How to run:

  • install SWIG (tested with version 3.0.12)
  • build using make -f GNUMakefilePyGeNN pygenn
  • add pygenn folder to your PYTHONPATH
  • enjoy

TODO by @neworderofjamie before merging

  • Test on Mac OSX
  • Add build infrastructure for Windows
  • Investigate packaging as prebuild package for pip
  • Figure out what to do about testing
  • Numpy implementation of connection tuples to sparse matrix.

…on and NNmodel::addSynapsePopulation, adapt generateALL main() for swig
…offers an end-to-end solution now (define simulation, build it, run, and get results as numpy array)
…es via numpy arrays; update GeNNModel.py wrapper to account for these changes
The helper functions to create Param and VarValues are now in their respective
classes added as swig extensions instead of being standalone.
introduce a better way to determine whether the model is Custom;
change Values type to dict instead of list.
other minor changes caused by ongoing integration with PyNN
…pygenn population (grougs)s to their own classes; minor bug fixes and improvements
make assingment of the numpy arrays to C pointers more generic
fix current spikes extraction
rework swig interface generator completely, many SWIG interface files
are now autogenerated;
rename libgenn to pygenn to distinguish it from pure C++ libgenn;
add virtual destructor to Snippet::Base to avoid memory leaks when using
SWIG's directors feature;
add support for variable initialization using InitVarSnippets;
add custom initVarSnippet;
Copy link
Contributor

@neworderofjamie neworderofjamie left a comment

Choose a reason for hiding this comment

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

This generally looks pretty good. There are a few changes I'd like made with the Makefile and the #ifdef SWIG stuff but otherwise it's just a matter of places where more comments are needed - especially that giant python module 😨

@@ -51,3 +51,9 @@ void chooseDevice(NNmodel &model, //!< the nn model we are generating code
const string &path, //!< path the generated code will be deposited
int localHostID); //!< ID of local host
#endif

#ifdef SWIG
Copy link
Contributor

Choose a reason for hiding this comment

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

This seems a bit messy. I think it would be much nicer if the stuff you've added in init_cuda_mpi could go in the entry-point code for your SWIG module (I'm guessing that SWIG modules like native python modules have this) and finalize_model_runner_generation could go into the SWIG module as well.

Copy link
Contributor Author

@braintimeException braintimeException Aug 9, 2018

Choose a reason for hiding this comment

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

This makes a lot of sense. I do not like the current solution either 😅

@@ -233,9 +233,11 @@ class NNmodel
const typename NeuronModel::VarValues &varInitialisers,
int hostID = 0, int deviceID = 0)
{
#ifndef SWIG
Copy link
Contributor

Choose a reason for hiding this comment

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

Likewise, if you had some entry-point code for your SWIG module you could just call initGeNN from there and get rid of the need for this (I realise initGeNN is pretty crap and it is going to be deprecated but still)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Initially I had initGeNN wrapped, but after careful examination I realized that all it does is initializing legacy models and as you said will most likely be deprecated. I thought it was a good idea not to include it in SWIG, but I can revert it back.

Copy link
Contributor

Choose a reason for hiding this comment

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

I think it's better to include it (or at least just add a hack that sets GeNNReady= 1) than to scatter #ifdefs everywhere

// Retrive symbols to pull/push from/to the device from shared model
// and store them in a map for fast lookup
void initIO( const std::string &popName,
const std::array<bool, 5> &availableDTypes )
Copy link
Contributor

Choose a reason for hiding this comment

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

It is super not-important but you could potentially use a std::bitset<5> here

Copy link
Contributor Author

Choose a reason for hiding this comment

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

👍

VoidFunction m_StepTimeCPU;
VoidFunction m_ExitGeNN;

PopIOMap populationsIO;
Copy link
Contributor

Choose a reason for hiding this comment

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

This should be m_PopulationsIO in keeping with the general naming convention.

lib/GNUmakefile Outdated
@@ -22,6 +22,9 @@
# Include makefile which builds libgenn
include $(GENN_PATH)/lib/GNUMakefileLibGeNN

# Include makefile which builds python wrapper
include $(GENN_PATH)/lib/GNUMakefilePyGeNN
Copy link
Contributor

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 nicer if GNUMakefilePyNN included GNUMakefileLibGeNN rather than the other way around

Copy link
Contributor Author

Choose a reason for hiding this comment

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

GNUMakefilePyNN implicitly includes GNUMakefileLibGeNN by coming after it. However many variables are not very helpful because they are libgenn specific. For example, LINK_FLAGS contain -lgenn which is not needed and not built if the target is pygenn.

Copy link
Contributor

Choose a reason for hiding this comment

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

I know, but the point of GNUMakefile to to build generateAll (which admittedly may not be clear from it's current name...) and generateAll links to libgenn so its makefile includes GNUMakefileLibGeNN. Likewise the point of GNUMakefilePyGeNN is to build PyGeNN so it should include GNUMakefileLibGeNN rather than be part of GNUMakefile

Copy link
Contributor

@neworderofjamie neworderofjamie Aug 9, 2018

Choose a reason for hiding this comment

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

How come pygenn doesn't link against libgenn or is that just not how SWIG works?

PYGENN_LINK_FLAGS +=$(shell mpiCC -showme:link)
endif

PYGENN_PIC_OBJ :=global modelSpec neuronGroup synapseGroup currentSource initVarSnippet utils codeStream codeGenUtils sparseUtils neuronModels postSynapseModels synapseModels newNeuronModels newPostsynapticModels newWeightUpdateModels currentSourceModels standardSubstitutions standardGeneratedSections generateALL generateCPU generateInit generateKernels generateMPI generateRunner
Copy link
Contributor

Choose a reason for hiding this comment

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

If you included GNUMakefileLibGeNN as I suggest below, I'm fairly sure this is just a copy of LIBGENN_OBJ. It would be REALLY good if we didn't have to maintain that list in more places (having it in the Windows and *nix build system is bad enough)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

it is a copy of original LIBGENN_OBJ however the LIBGENN_OBJ are already fully qualified paths by this point.

Copy link
Contributor

Choose a reason for hiding this comment

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

Yeah I'd be more than happy if you made GNUMakefileLibGeNN more flexible i.e. by keeping the unqualified paths separate.

Copy link
Contributor

Choose a reason for hiding this comment

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

Another issue with this makefile structure that, when you try and do something else with GeNN and you it can't find your python/numpy source, you can't run genn-build-model.sh!

cp $(SWIG_PATH)/GeNNModel.py $(PYGENN_PATH)/GeNNModel.py
cp $(SWIG_PATH)/GeNNGroups.py $(PYGENN_PATH)/GeNNGroups.py

$(PYGENN_PATH)/_StlContainers.so: $(PYGENN_PATH)/stl_containers_wrap.cc
Copy link
Contributor

Choose a reason for hiding this comment

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

It would be really good if (after you include GNUMakefileLibGeNN 😄) you used the LIBGENN_PREFIX to make a unique object directory for each variant of the modules. Otherwise it's very easy to get into a mess of incompatible bits of CPU_ONLY and normal versions mixed together.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I will try to adapt both make files to minimize redundancy

class ParamValues
{
public:
template<typename T>
Copy link
Contributor

Choose a reason for hiding this comment

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

How come this is templated?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Well, it is just a Snippet::ValueBase with a relaxed constructor... And yes, the template here is absolutely unnecessary 😅

Copy link
Contributor

Choose a reason for hiding this comment

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

Hahah

@@ -0,0 +1,37 @@
#ifndef CUSTOMPARAMVALUES_H
Copy link
Contributor

Choose a reason for hiding this comment

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

We're generally trying to switch to using #pragma once rather than old style include guards

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I wasn't aware of #pragma once!

@@ -0,0 +1,538 @@
import sys
Copy link
Contributor

Choose a reason for hiding this comment

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

What does this file do? It needs a lot of commenting!

Copy link
Contributor Author

Choose a reason for hiding this comment

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

it generates interface files for SWIG. Some interfaces require many templates specializations and the interfaces for models look alike. So this file does all the copy-paste work 😃 Additionally it reads the models files and creates template specializations for them, so when a new model is added to GeNN, it will wrap it without any additional work.

Copy link
Contributor

Choose a reason for hiding this comment

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

It would also be really nice if this stuff generated pydoc help - especially as someone who doesn't know how it works help(pygenn.NNmodel.addNeuronPopulation) gives me no clues as to what parameters are

Copy link
Contributor Author

Choose a reason for hiding this comment

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

actually everything is supposed to work via GeNNModel class

Copy link
Contributor

Choose a reason for hiding this comment

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

that explains why that wasn't working then 😄

SWIG_PATH :=$(GENN_PATH)/lib/swig
PYGENN_PATH :=$(GENN_PATH)/lib/pygenn
PYGENN_SWIG_FILE_PATH :=$(GENN_PATH)/lib/$(SWIG_MODULE_NAME).i
PYTHON_INCLUDE ?=$(shell python-config --includes)
Copy link
Contributor

Choose a reason for hiding this comment

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

For me, this returns: -I/usr/include/python2.7 -I/usr/include/x86_64-linux-gnu/python2.7 so when you do -I $(PYTHON_INCLUDE) lower down it fails

Copy link
Contributor

Choose a reason for hiding this comment

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

(it builds if I override it to /usr/include/python2.7 from the command line though)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Right... Python 2.7 is still the standard on many systems, isn't it?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Oh, so the issue is not the version, but the second include? I also have two, but they are the same on my system... I guess, taking only the first one should solve this.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Hah indeed I do add -I... I wonder why it works 🤔

Copy link
Contributor Author

@braintimeException braintimeException Aug 9, 2018

Choose a reason for hiding this comment

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

By the way I cannot guarantee that Python 2.7 will work... Should we maybe restrict it to 3?

Copy link
Contributor

@neworderofjamie neworderofjamie Aug 10, 2018

Choose a reason for hiding this comment

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

I'm still figuring out how to actually do anything with it once it's imported, but it seems to work in Python 2.7. I don't really think restricting stuff to Python 3 is a legitimate option - a lot of people still use 2.7 (myself included)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I will test it with 2.7.

@neworderofjamie
Copy link
Contributor

neworderofjamie commented Aug 9, 2018 via email

@neworderofjamie
Copy link
Contributor

neworderofjamie commented Aug 9, 2018 via email

"""
self.initRequired = False
if initVar is not None:
setRandomInit( initVar, values )
Copy link
Contributor

Choose a reason for hiding this comment

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

Where is this function located and what does it do? Are InitVarSnippets implemented or should that be added to the Todo?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

InitVarSnippets were the last thing I implemented (actually just yesterday) and are not thoroughly tested, but they hopefully work. self.initRequired's purpose is to indicate that a list of values was provided and if it is true, these values will be pushed to device when the model is loaded.

Copy link
Contributor

Choose a reason for hiding this comment

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

Cool that they're implemented! What I meant though is where is setRandomInit? I can't find it in the module...

Copy link
Contributor Author

Choose a reason for hiding this comment

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

oops that's a typo. It must read self.setInitVar.

Copy link
Contributor

Choose a reason for hiding this comment

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

👍


for popName, popData in self.synapsePopulations.items():
if popData.sparse:
popData.pop.setMaxConnections( popData.size )
Copy link
Contributor

@neworderofjamie neworderofjamie Aug 10, 2018

Choose a reason for hiding this comment

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

For sparse projections, max connections means (I realise it's badly named - sorry) the largest number of postsynaptic neurons any presynaptic neuron is connected to i.e. the longest length of a sparse synaptic matrix row - on that basis I'm not sure if this is correct.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

oh. I clearly misunderstood the name.

Copy link
Contributor

Choose a reason for hiding this comment

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

No worries - that one is clearly Thomas's fault 😄 It's important to get that one right as it defines how many CUDA threads are used to simulate the synapses

@tnowotny
Copy link
Member

agreed!

@neworderofjamie neworderofjamie merged commit a286a09 into master Nov 23, 2018
@neworderofjamie neworderofjamie deleted the python_wrapper branch November 23, 2018 13:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants