Introduction to NDComponents in NeuroDriver
In this hackpad, we introduce the main concept behind NDComponents and provide examples of several types of components. 
 
The NDComponent Class
The NDComponent class deals with implementations of component models. Here, the computation all takes place on the GPU. This class is introduced to
  1. keep a natural mapping from the data model in NeuroArch to actual implementation,
  1. make it easier for users to implement individual component models, i.e., the API takes care of most of the tedious jobs, such as memory management,
  1. allow the LPU class to easily create an LPU from a graph-based specification rather than constructing an LPU directly from component instances.
 
In order to leverage the parallel computing power of GPUs, components that uses the same models, e.g., all Leaky-Integrate-and-Fire neurons, are grouped together and executed in parallel in a single CUDA kernel. Therefore, a subclass of the NDComponent should implement the component model in parallel fashion.
 
The NDComponent Class is an abstract base class. Currently, it requires two class variables and two methods to be fully implemented by a subclass. 
 
Accesses and Updates
A component needs to access the value of some variables from other components, and update some variables of its own. For example, a synapse component accesses the spike state of a presynaptic neuron and updates its conductance value, an axon hillock component accesses the currents generated by dendrites and updates the spike state as its output.  
 
This is the basic idea of the variable system in the NDComponent class and through out the LPU implementation in NeuroDriver. The implementation of a subclass of NDComponent must specify two class variables, namely, accesses and updates.
 
The variable accesses is a list of variables that the component needs to access. For example, the typical accesses variable for point neuron models is (synaptic and injected) current:
accesses = ['I']
The variable updates is a list of variables that the component should update, i.e., these are the output variables of the component. For example, the typical updates variable for a class of spiking neurons is spike state and membrane voltage:
updates = ['spike_state', 'V']
 
The use of this variable system makes it convenient to perform the following tasks:
  1. Compatibility of two connected components can be automatically checked. If component A connects to component B, but the intersection between updates of A and accesses of B is an empty set, then this connection is not compatible. If the intersection is a unique variable, then the variable that passes from A to B does not have to be explicitly specified. If the intersection set has more than one variable, one needs to specify the variable to be passed for this edge in the graph defining the LPU.
  1. An LPU instance can collect the correct variables, find appropriate parts of memory and provide these information to the components. We will come back to this point later in this hackpad.
 
  • Examples
  1. Leaky-Integrate-and-Fire (LIF) point neuron model:
accesses = ['I']
updates = ['spike_state', 'V']
  1. Alpha synapse model:
accesses = ['spike_state']
updates = ['g'] # conductance of the synapse
  1. A simple dendrite model / synaptic current model:
accesses = ['g', 'V']
updates = ['I']
  • Note that it needs to access the conductance value of the synapse and the post-synaptic neuron membrane potential, as the equation is I = g (V - E), where E is the reversal potential of the synapse.
  1. gap junction:
accesses = ['V']
updates = ['I']
  1.  
 
Memory Access
Let us take a look at the variable specification and memory structure of an instance of NDComponent subclass. 
 
Based on the accesses variable, LPU will provide access_buffers that contains the buffers holding recent values of the required variables. LPU will also provide parameters of the components using variable params_dict. Note that both access_buffers and params_dict are dictionaries, thereby can be accessed through a variable/parameter key. Both are provided when instance the instance is constructed. Users should also allocate memory space for internal variables of the components. For example, Hodgkin-Huxley point neuron model has 4 states: V, n, m, h. Only the membrane potential V is the output, while the other variables are typically not accessible by other components. As such, the variables n, m, h are treated as internal variables and they should be created in the __init__ method.
 
Memory manager will provide, for each variable in the updates, a continuous chunk of memory in the GPU array to each component for updating its states. The starting address of relevant chunks of memory are provided as the update_pointers variable in the run_step method. The update_pointers is a dictionary, e.g., update_pointers['V'] will return the GPU memory address for which you can update the membrane voltages of components in this class.