perforatedai.network_perforatedai

  1from perforatedai import globals_perforatedai as GPA
  2from perforatedai import utils_perforatedai as UPA
  3import sys
  4
  5from safetensors.torch import load_file
  6import copy
  7
  8import torch.nn as nn
  9import torch
 10import pdb
 11
 12from threading import Thread
 13
 14
 15doing_threading = False
 16loaded_full_print = False
 17
 18
 19def convert_network(net, layer_name=""):
 20    """Convert a model to use PAI perforated wrappers.
 21
 22    Parameters
 23    ----------
 24    net : nn.Module
 25        Model or submodule to convert.
 26    layer_name : str, optional
 27        Required name when converting a single module directly.
 28
 29    Returns
 30    -------
 31    nn.Module
 32        Converted module tree.
 33    """
 34    # If the net itself has a substitution make that substitution first
 35    if type(net) in GPA.pc.get_modules_to_replace():
 36        net = UPA.replace_predefined_modules(net)
 37    # If the net itself should be converted make the converstion
 38    if type(net) in GPA.pc.get_modules_to_perforate():
 39        if layer_name == "":
 40            print(
 41                "converting a single layer without a name, add a layer_name param to the call"
 42            )
 43            sys.exit(-1)
 44        net = PerforatedModule(net, layer_name)
 45    # Otherwise, check the module recursively if there are other modules to convert
 46    else:
 47        net = UPA.convert_module(net, 0, "", [], [], PerforatedModule, PAITrackedModule)
 48    return net
 49
 50
 51def get_pai_modules(net, depth, seen_ids=None):
 52    """Collect unique PerforatedModule instances from a module tree.
 53
 54    Parameters
 55    ----------
 56    net : nn.Module
 57        Root module to traverse.
 58    depth : int
 59        Current recursion depth.
 60    seen_ids : set or None, optional
 61        Set of module object IDs already collected.
 62
 63    Returns
 64    -------
 65    list
 66        List of unique ``PerforatedModule`` objects.
 67    """
 68    if seen_ids is None:
 69        seen_ids = set()
 70    all_members = net.__dir__()
 71    this_list = []
 72    if issubclass(type(net), nn.Sequential) or issubclass(type(net), nn.ModuleList):
 73        for submodule_id, layer in net.named_children():
 74            if net.get_submodule(submodule_id) is net:
 75                continue
 76            if type(net.get_submodule(submodule_id)) is PerforatedModule:
 77                module = net.get_submodule(submodule_id)
 78                if id(module) in seen_ids:
 79                    continue
 80                seen_ids.add(id(module))
 81                this_list = this_list + [module]
 82            else:
 83                this_list = this_list + get_pai_modules(
 84                    net.get_submodule(submodule_id), depth + 1, seen_ids
 85                )
 86    else:
 87        for member in all_members:
 88            if isinstance(getattr(type(net), member, None), property):
 89                continue
 90            if getattr(net, member, None) is net:
 91                continue
 92            if type(getattr(net, member, None)) is PerforatedModule:
 93                module = getattr(net, member)
 94                if id(module) in seen_ids:
 95                    continue
 96                seen_ids.add(id(module))
 97                this_list = this_list + [module]
 98            elif issubclass(type(getattr(net, member, None)), nn.Module):
 99                this_list = this_list + get_pai_modules(
100                    getattr(net, member), depth + 1, seen_ids
101                )
102    return this_list
103
104
105def load_pai_model_from_dict(net, state_dict):
106    """Load PAI-specific module state from a state dictionary.
107
108    Parameters
109    ----------
110    net : nn.Module
111        Converted network containing perforated modules.
112    state_dict : dict
113        Serialized model state.
114
115    Returns
116    -------
117    nn.Module
118        Network with loaded state and reconstructed runtime buffers.
119    """
120    pai_modules = get_pai_modules(net, 0)
121    if pai_modules == []:
122        print("No PAI modules were found something went wrong with convert network")
123        pdb.set_trace()
124        sys.exit()
125    for module in pai_modules:
126        # Set up name to be what will be saved in the state dict
127        module_name = UPA.get_module_base_name(module)
128        # Then instantiate as many Dendrites as were created during training
129        num_cycles = int(state_dict[module_name + ".num_cycles"].item())
130        # extract node index from state_dict
131        nodeCount = 10
132        # also extract view tuple
133        if num_cycles > 0:
134            module.simulate_cycles(num_cycles, nodeCount)
135        if not module.processor is None:
136            processor = copy.deepcopy(module.processor)
137            processor.pre = module.processor.post_n1
138            processor.post = module.processor.post_n2
139            module.processor_array.append(processor)
140        else:
141            module.processor_array.append(None)
142
143        # Create ParameterList for skip_weights based on num_cycles
144        num_params = num_cycles // 2
145        skip_weights_list = nn.ParameterList()
146        for i in range(num_params):
147            param_key = module_name + f".skip_weights.{i}"
148            if param_key in state_dict:
149                param = nn.Parameter(torch.randn(state_dict[param_key].shape))
150                skip_weights_list.append(param)
151        module.skip_weights = skip_weights_list
152
153        # module.register_buffer('skip_weights', torch.zeros(state_dict[module_name + '.skip_weights'].shape))
154        module.register_buffer("view_tuple", state_dict[module_name + ".view_tuple"])
155
156    net.load_state_dict(state_dict)
157
158    for module in pai_modules:
159        temp = tuple(module.view_tuple.tolist())
160        del module.view_tuple
161        module.view_tuple = temp
162
163    return net
164    # figure out if doing this 'thread' stuff is actually helping at all.
165    # If its not just get rid of it to simplify things.
166    # to test this will have to first get load_pai_model actually set up and working then run a test with and #without threading.
167
168
169def load_pai_model(net, filename):
170    """Load a saved PAI model file into a converted network.
171
172    Parameters
173    ----------
174    net : nn.Module
175        Base network architecture.
176    filename : str
177        Path to a safetensors state file.
178
179    Returns
180    -------
181    nn.Module
182        Loaded PAI network.
183    """
184    net = convert_network(net)
185    state_dict = load_file(filename)
186    return load_pai_model_from_dict(net, state_dict)
187
188
189class PerforatedModule(nn.Module):
190    def __init__(self, original_module, name):
191        """Initialize a perforated wrapper around a single module.
192
193        Parameters
194        ----------
195        original_module : nn.Module
196            Original module being wrapped.
197        name : str
198            Qualified module name used for save/load mapping.
199        """
200        super(PerforatedModule, self).__init__()
201        self.name = name
202        self.register_buffer("node_index", torch.tensor(-1))
203        self.register_buffer("num_cycles", torch.tensor(-1))
204        self.register_buffer("view_tuple", torch.tensor(-1))
205        self.processor_array = []
206        self.processor = None
207        self.layer_array = nn.ModuleList([original_module])
208        # If this original module has processing functions save the processor
209        if type(original_module) in GPA.pc.get_modules_with_processing():
210            module_index = GPA.pc.get_modules_with_processing().index(
211                type(original_module)
212            )
213            self.processor = GPA.pc.get_modules_processing_classes()[module_index]()
214        elif (
215            type(original_module).__name__ in GPA.pc.get_module_names_with_processing()
216        ):
217            module_index = GPA.pc.get_module_names_with_processing().index(
218                type(original_module).__name__
219            )
220            self.processor = GPA.pc.get_module_by_name_processing_classes()[
221                module_index
222            ]()
223
224    def simulate_cycles(self, num_cycles, nodeCount):
225        """Expand internal layer/processor lists for stored dendrite cycles.
226
227        Parameters
228        ----------
229        num_cycles : int
230            Number of perforation cycles represented in saved state.
231        nodeCount : int
232            Node count hint kept for interface compatibility.
233
234        Returns
235        -------
236        None
237            This function does not return a value.
238        """
239        for i in range(0, num_cycles, 2):
240            self.layer_array.append(copy.deepcopy(self.layer_array[0]))
241            if not self.processor is None:
242                processor = copy.deepcopy(self.processor)
243                processor.pre = self.processor.pre_d
244                processor.post = self.processor.post_d
245                self.processor_array.append(processor)
246            else:
247                self.processor_array.append(None)
248
249    def process_and_forward(self, *args2, **kwargs2):
250        """Execute one dendrite layer and write output into shared storage.
251
252        Parameters
253        ----------
254        *args2 : tuple
255            Positional values where first entries are layer index and output
256            buffer, followed by layer inputs.
257        **kwargs2 : dict
258            Keyword arguments forwarded to the wrapped layer.
259
260        Returns
261        -------
262        None
263            This function does not return a value.
264        """
265        c = args2[0]
266        dendrite_outs = args2[1]
267        args2 = args2[2:]
268        if self.processor_array[c] != None:
269            out_values = self.processor_array[c].pre(*args2, **kwargs2)
270        out_values = self.layer_array[c](*args2, **kwargs2)
271        if self.processor_array[c] != None:
272            out = self.processor_array[c].post(out_values)
273        else:
274            out = out_values
275        dendrite_outs[c] = out
276
277    def process_and_pre(self, *args, **kwargs):
278        """Run the final pre-dendrite layer pass and cache its output.
279
280        Parameters
281        ----------
282        *args : tuple
283            Positional values with output buffer first, then model inputs.
284        **kwargs : dict
285            Keyword arguments forwarded to the wrapped layer.
286
287        Returns
288        -------
289        None
290            This function does not return a value.
291        """
292        dendrite_outs = args[0]
293        args = args[1:]
294        out = self.layer_array[-1].forward(*args, **kwargs)
295        if not self.processor_array[-1] is None:
296            out = self.processor_array[-1].pre(out)
297        dendrite_outs[len(self.layer_array) - 1] = out
298
299    def forward(self, *args, **kwargs):
300        """Run perforated forward pass with dendrite accumulation.
301
302        Parameters
303        ----------
304        *args : tuple
305            Positional arguments forwarded through wrapped layers.
306        **kwargs : dict
307            Keyword arguments forwarded through wrapped layers.
308
309        Returns
310        -------
311        Any
312            Final model output after optional processor post-processing.
313        """
314        # this is currently false anyway, just remove the doing multi idea
315        doing_multi = doing_threading
316        dendrite_outs = [None] * len(self.layer_array)
317        threads = {}
318        for c in range(0, len(self.layer_array) - 1):
319            args2, kwargs2 = args, kwargs
320            if doing_multi:
321                threads[c] = Thread(
322                    target=self.process_and_forward,
323                    args=(c, dendrite_outs, *args),
324                    kwargs=kwargs,
325                )
326            else:
327                self.process_and_forward(c, dendrite_outs, *args2, **kwargs2)
328        if doing_multi:
329            threads[len(self.layer_array) - 1] = Thread(
330                target=self.process_and_pre, args=(dendrite_outs, *args), kwargs=kwargs
331            )
332        else:
333            self.process_and_pre(dendrite_outs, *args, **kwargs)
334        if doing_multi:
335            for i in range(len(dendrite_outs)):
336                threads[i].start()
337            for i in range(len(dendrite_outs)):
338                threads[i].join()
339        for out_index in range(0, len(self.layer_array)):
340            current_out = dendrite_outs[out_index]
341
342            if len(self.layer_array) > 1 and hasattr(self, "skip_weights"):
343                for in_index in range(0, out_index):
344                    # Use out_index - 1 because skip_weights[0] is never used
345                    current_out = (
346                        current_out
347                        + self.skip_weights[out_index - 1][in_index, :]
348                        .reshape(self.view_tuple)
349                        .to(current_out.device)
350                        * dendrite_outs[in_index]
351                    )
352                if out_index < len(self.layer_array) - 1:
353                    current_out = GPA.pc.get_pai_forward_function()(current_out)
354            dendrite_outs[out_index] = current_out
355        if not self.processor_array[-1] is None:
356            current_out = self.processor_array[-1].post(current_out)
357        return current_out
358
359
360class PAITrackedModule(nn.Module):
361    """Wrapper for modules you don't want to add dendrites to. Ensures all modules are accounted for."""
362
363    def __init__(self, start_module, name):
364        """Initialize PAITrackedModule.
365
366        This function sets up the tracked neuron module to wrap the start_module
367        without adding dendrites.
368
369        Parameters
370        ----------
371        start_module : nn.Module
372            The module to wrap.
373        name : str
374            The name of the neuron module.
375        """
376        super(PAITrackedModule, self).__init__()
377
378        if isinstance(start_module, nn.Module):
379            self.main_module = start_module
380        else:
381            print("start_module must be nn.Module: %s" % name)
382            print(type(start_module))
383            print(start_module)
384            sys.exit(-1)
385        self.name = name
386
387        self.type = "tracked_module"
388
389    def __getattr__(self, name):
390        """Get member variables from the main module.
391
392        Parameters
393        ----------
394        name : str
395            The name of the variable to retrieve.
396        Returns
397        -------
398        The requested variable.
399
400        Notes
401        -----
402        This method first attempts to retrieve the attribute from the PAINeuronModule instance.
403        If it fails, it tries to get the attribute from the wrapped main_module.
404        This allows seamless access to the main module's attributes without modifying original code.
405        """
406        try:
407            return super().__getattr__(name)
408        except AttributeError:
409            return getattr(self.main_module, name)
410
411    def forward(self, *args, **kwargs):
412        """Forward pass for tracked layer.
413
414        Parameters
415        ----------
416        *args : tuple
417            Positional arguments for the forward pass.
418        **kwargs : dict
419            Keyword arguments for the forward pass.
420
421        Returns
422        -------
423        Any
424            The output of the module
425
426        Notes
427        -----
428            The output of this forward function will have the same format as the output
429            of the original module
430        """
431        return self.main_module(*args, **kwargs)
432
433    def __str__(self):
434        """String representation of the layer.
435
436        Parameters
437        ----------
438        None
439
440        Returns
441        -------
442        str
443            String representation of the layer.
444
445        Notes
446        -----
447        Setting for verbose changes level of details in the string output.
448        """
449
450        if GPA.pc.get_verbose():
451            total_string = self.main_module.__str__()
452            total_string = "PAITrackedLayer(" + total_string + ")"
453            return total_string
454        else:
455            total_string = self.main_module.__str__()
456            total_string = "PAITrackedLayer(" + total_string + ")"
457            return total_string
458
459    def __repr__(self):
460        """Representation of the layer."""
461        return self.__str__()
doing_threading = False
loaded_full_print = False
def convert_network(net, layer_name=''):
20def convert_network(net, layer_name=""):
21    """Convert a model to use PAI perforated wrappers.
22
23    Parameters
24    ----------
25    net : nn.Module
26        Model or submodule to convert.
27    layer_name : str, optional
28        Required name when converting a single module directly.
29
30    Returns
31    -------
32    nn.Module
33        Converted module tree.
34    """
35    # If the net itself has a substitution make that substitution first
36    if type(net) in GPA.pc.get_modules_to_replace():
37        net = UPA.replace_predefined_modules(net)
38    # If the net itself should be converted make the converstion
39    if type(net) in GPA.pc.get_modules_to_perforate():
40        if layer_name == "":
41            print(
42                "converting a single layer without a name, add a layer_name param to the call"
43            )
44            sys.exit(-1)
45        net = PerforatedModule(net, layer_name)
46    # Otherwise, check the module recursively if there are other modules to convert
47    else:
48        net = UPA.convert_module(net, 0, "", [], [], PerforatedModule, PAITrackedModule)
49    return net

Convert a model to use PAI perforated wrappers.

Parameters
  • net (nn.Module): Model or submodule to convert.
  • layer_name (str, optional): Required name when converting a single module directly.
Returns
  • nn.Module: Converted module tree.
def get_pai_modules(net, depth, seen_ids=None):
 52def get_pai_modules(net, depth, seen_ids=None):
 53    """Collect unique PerforatedModule instances from a module tree.
 54
 55    Parameters
 56    ----------
 57    net : nn.Module
 58        Root module to traverse.
 59    depth : int
 60        Current recursion depth.
 61    seen_ids : set or None, optional
 62        Set of module object IDs already collected.
 63
 64    Returns
 65    -------
 66    list
 67        List of unique ``PerforatedModule`` objects.
 68    """
 69    if seen_ids is None:
 70        seen_ids = set()
 71    all_members = net.__dir__()
 72    this_list = []
 73    if issubclass(type(net), nn.Sequential) or issubclass(type(net), nn.ModuleList):
 74        for submodule_id, layer in net.named_children():
 75            if net.get_submodule(submodule_id) is net:
 76                continue
 77            if type(net.get_submodule(submodule_id)) is PerforatedModule:
 78                module = net.get_submodule(submodule_id)
 79                if id(module) in seen_ids:
 80                    continue
 81                seen_ids.add(id(module))
 82                this_list = this_list + [module]
 83            else:
 84                this_list = this_list + get_pai_modules(
 85                    net.get_submodule(submodule_id), depth + 1, seen_ids
 86                )
 87    else:
 88        for member in all_members:
 89            if isinstance(getattr(type(net), member, None), property):
 90                continue
 91            if getattr(net, member, None) is net:
 92                continue
 93            if type(getattr(net, member, None)) is PerforatedModule:
 94                module = getattr(net, member)
 95                if id(module) in seen_ids:
 96                    continue
 97                seen_ids.add(id(module))
 98                this_list = this_list + [module]
 99            elif issubclass(type(getattr(net, member, None)), nn.Module):
100                this_list = this_list + get_pai_modules(
101                    getattr(net, member), depth + 1, seen_ids
102                )
103    return this_list

Collect unique PerforatedModule instances from a module tree.

Parameters
  • net (nn.Module): Root module to traverse.
  • depth (int): Current recursion depth.
  • seen_ids (set or None, optional): Set of module object IDs already collected.
Returns
def load_pai_model_from_dict(net, state_dict):
106def load_pai_model_from_dict(net, state_dict):
107    """Load PAI-specific module state from a state dictionary.
108
109    Parameters
110    ----------
111    net : nn.Module
112        Converted network containing perforated modules.
113    state_dict : dict
114        Serialized model state.
115
116    Returns
117    -------
118    nn.Module
119        Network with loaded state and reconstructed runtime buffers.
120    """
121    pai_modules = get_pai_modules(net, 0)
122    if pai_modules == []:
123        print("No PAI modules were found something went wrong with convert network")
124        pdb.set_trace()
125        sys.exit()
126    for module in pai_modules:
127        # Set up name to be what will be saved in the state dict
128        module_name = UPA.get_module_base_name(module)
129        # Then instantiate as many Dendrites as were created during training
130        num_cycles = int(state_dict[module_name + ".num_cycles"].item())
131        # extract node index from state_dict
132        nodeCount = 10
133        # also extract view tuple
134        if num_cycles > 0:
135            module.simulate_cycles(num_cycles, nodeCount)
136        if not module.processor is None:
137            processor = copy.deepcopy(module.processor)
138            processor.pre = module.processor.post_n1
139            processor.post = module.processor.post_n2
140            module.processor_array.append(processor)
141        else:
142            module.processor_array.append(None)
143
144        # Create ParameterList for skip_weights based on num_cycles
145        num_params = num_cycles // 2
146        skip_weights_list = nn.ParameterList()
147        for i in range(num_params):
148            param_key = module_name + f".skip_weights.{i}"
149            if param_key in state_dict:
150                param = nn.Parameter(torch.randn(state_dict[param_key].shape))
151                skip_weights_list.append(param)
152        module.skip_weights = skip_weights_list
153
154        # module.register_buffer('skip_weights', torch.zeros(state_dict[module_name + '.skip_weights'].shape))
155        module.register_buffer("view_tuple", state_dict[module_name + ".view_tuple"])
156
157    net.load_state_dict(state_dict)
158
159    for module in pai_modules:
160        temp = tuple(module.view_tuple.tolist())
161        del module.view_tuple
162        module.view_tuple = temp
163
164    return net
165    # figure out if doing this 'thread' stuff is actually helping at all.
166    # If its not just get rid of it to simplify things.
167    # to test this will have to first get load_pai_model actually set up and working then run a test with and #without threading.

Load PAI-specific module state from a state dictionary.

Parameters
  • net (nn.Module): Converted network containing perforated modules.
  • state_dict (dict): Serialized model state.
Returns
  • nn.Module: Network with loaded state and reconstructed runtime buffers.
def load_pai_model(net, filename):
170def load_pai_model(net, filename):
171    """Load a saved PAI model file into a converted network.
172
173    Parameters
174    ----------
175    net : nn.Module
176        Base network architecture.
177    filename : str
178        Path to a safetensors state file.
179
180    Returns
181    -------
182    nn.Module
183        Loaded PAI network.
184    """
185    net = convert_network(net)
186    state_dict = load_file(filename)
187    return load_pai_model_from_dict(net, state_dict)

Load a saved PAI model file into a converted network.

Parameters
  • net (nn.Module): Base network architecture.
  • filename (str): Path to a safetensors state file.
Returns
  • nn.Module: Loaded PAI network.
class PerforatedModule(torch.nn.modules.module.Module):
190class PerforatedModule(nn.Module):
191    def __init__(self, original_module, name):
192        """Initialize a perforated wrapper around a single module.
193
194        Parameters
195        ----------
196        original_module : nn.Module
197            Original module being wrapped.
198        name : str
199            Qualified module name used for save/load mapping.
200        """
201        super(PerforatedModule, self).__init__()
202        self.name = name
203        self.register_buffer("node_index", torch.tensor(-1))
204        self.register_buffer("num_cycles", torch.tensor(-1))
205        self.register_buffer("view_tuple", torch.tensor(-1))
206        self.processor_array = []
207        self.processor = None
208        self.layer_array = nn.ModuleList([original_module])
209        # If this original module has processing functions save the processor
210        if type(original_module) in GPA.pc.get_modules_with_processing():
211            module_index = GPA.pc.get_modules_with_processing().index(
212                type(original_module)
213            )
214            self.processor = GPA.pc.get_modules_processing_classes()[module_index]()
215        elif (
216            type(original_module).__name__ in GPA.pc.get_module_names_with_processing()
217        ):
218            module_index = GPA.pc.get_module_names_with_processing().index(
219                type(original_module).__name__
220            )
221            self.processor = GPA.pc.get_module_by_name_processing_classes()[
222                module_index
223            ]()
224
225    def simulate_cycles(self, num_cycles, nodeCount):
226        """Expand internal layer/processor lists for stored dendrite cycles.
227
228        Parameters
229        ----------
230        num_cycles : int
231            Number of perforation cycles represented in saved state.
232        nodeCount : int
233            Node count hint kept for interface compatibility.
234
235        Returns
236        -------
237        None
238            This function does not return a value.
239        """
240        for i in range(0, num_cycles, 2):
241            self.layer_array.append(copy.deepcopy(self.layer_array[0]))
242            if not self.processor is None:
243                processor = copy.deepcopy(self.processor)
244                processor.pre = self.processor.pre_d
245                processor.post = self.processor.post_d
246                self.processor_array.append(processor)
247            else:
248                self.processor_array.append(None)
249
250    def process_and_forward(self, *args2, **kwargs2):
251        """Execute one dendrite layer and write output into shared storage.
252
253        Parameters
254        ----------
255        *args2 : tuple
256            Positional values where first entries are layer index and output
257            buffer, followed by layer inputs.
258        **kwargs2 : dict
259            Keyword arguments forwarded to the wrapped layer.
260
261        Returns
262        -------
263        None
264            This function does not return a value.
265        """
266        c = args2[0]
267        dendrite_outs = args2[1]
268        args2 = args2[2:]
269        if self.processor_array[c] != None:
270            out_values = self.processor_array[c].pre(*args2, **kwargs2)
271        out_values = self.layer_array[c](*args2, **kwargs2)
272        if self.processor_array[c] != None:
273            out = self.processor_array[c].post(out_values)
274        else:
275            out = out_values
276        dendrite_outs[c] = out
277
278    def process_and_pre(self, *args, **kwargs):
279        """Run the final pre-dendrite layer pass and cache its output.
280
281        Parameters
282        ----------
283        *args : tuple
284            Positional values with output buffer first, then model inputs.
285        **kwargs : dict
286            Keyword arguments forwarded to the wrapped layer.
287
288        Returns
289        -------
290        None
291            This function does not return a value.
292        """
293        dendrite_outs = args[0]
294        args = args[1:]
295        out = self.layer_array[-1].forward(*args, **kwargs)
296        if not self.processor_array[-1] is None:
297            out = self.processor_array[-1].pre(out)
298        dendrite_outs[len(self.layer_array) - 1] = out
299
300    def forward(self, *args, **kwargs):
301        """Run perforated forward pass with dendrite accumulation.
302
303        Parameters
304        ----------
305        *args : tuple
306            Positional arguments forwarded through wrapped layers.
307        **kwargs : dict
308            Keyword arguments forwarded through wrapped layers.
309
310        Returns
311        -------
312        Any
313            Final model output after optional processor post-processing.
314        """
315        # this is currently false anyway, just remove the doing multi idea
316        doing_multi = doing_threading
317        dendrite_outs = [None] * len(self.layer_array)
318        threads = {}
319        for c in range(0, len(self.layer_array) - 1):
320            args2, kwargs2 = args, kwargs
321            if doing_multi:
322                threads[c] = Thread(
323                    target=self.process_and_forward,
324                    args=(c, dendrite_outs, *args),
325                    kwargs=kwargs,
326                )
327            else:
328                self.process_and_forward(c, dendrite_outs, *args2, **kwargs2)
329        if doing_multi:
330            threads[len(self.layer_array) - 1] = Thread(
331                target=self.process_and_pre, args=(dendrite_outs, *args), kwargs=kwargs
332            )
333        else:
334            self.process_and_pre(dendrite_outs, *args, **kwargs)
335        if doing_multi:
336            for i in range(len(dendrite_outs)):
337                threads[i].start()
338            for i in range(len(dendrite_outs)):
339                threads[i].join()
340        for out_index in range(0, len(self.layer_array)):
341            current_out = dendrite_outs[out_index]
342
343            if len(self.layer_array) > 1 and hasattr(self, "skip_weights"):
344                for in_index in range(0, out_index):
345                    # Use out_index - 1 because skip_weights[0] is never used
346                    current_out = (
347                        current_out
348                        + self.skip_weights[out_index - 1][in_index, :]
349                        .reshape(self.view_tuple)
350                        .to(current_out.device)
351                        * dendrite_outs[in_index]
352                    )
353                if out_index < len(self.layer_array) - 1:
354                    current_out = GPA.pc.get_pai_forward_function()(current_out)
355            dendrite_outs[out_index] = current_out
356        if not self.processor_array[-1] is None:
357            current_out = self.processor_array[-1].post(current_out)
358        return current_out

Base class for all neural network modules.

Your models should also subclass this class.

Modules can also contain other Modules, allowing them to be nested in a tree structure. You can assign the submodules as regular attributes::

import torch.nn as nn
import torch.nn.functional as F


class Model(nn.Module):
    def __init__(self) -> None:
        super().__init__()
        self.conv1 = nn.Conv2d(1, 20, 5)
        self.conv2 = nn.Conv2d(20, 20, 5)

    def forward(self, x):
        x = F.relu(self.conv1(x))
        return F.relu(self.conv2(x))

Submodules assigned in this way will be registered, and will also have their parameters converted when you call to(), etc.

As per the example above, an __init__() call to the parent class must be made before assignment on the child.

:ivar training: Boolean represents whether this module is in training or evaluation mode. :vartype training: bool

PerforatedModule(original_module, name)
191    def __init__(self, original_module, name):
192        """Initialize a perforated wrapper around a single module.
193
194        Parameters
195        ----------
196        original_module : nn.Module
197            Original module being wrapped.
198        name : str
199            Qualified module name used for save/load mapping.
200        """
201        super(PerforatedModule, self).__init__()
202        self.name = name
203        self.register_buffer("node_index", torch.tensor(-1))
204        self.register_buffer("num_cycles", torch.tensor(-1))
205        self.register_buffer("view_tuple", torch.tensor(-1))
206        self.processor_array = []
207        self.processor = None
208        self.layer_array = nn.ModuleList([original_module])
209        # If this original module has processing functions save the processor
210        if type(original_module) in GPA.pc.get_modules_with_processing():
211            module_index = GPA.pc.get_modules_with_processing().index(
212                type(original_module)
213            )
214            self.processor = GPA.pc.get_modules_processing_classes()[module_index]()
215        elif (
216            type(original_module).__name__ in GPA.pc.get_module_names_with_processing()
217        ):
218            module_index = GPA.pc.get_module_names_with_processing().index(
219                type(original_module).__name__
220            )
221            self.processor = GPA.pc.get_module_by_name_processing_classes()[
222                module_index
223            ]()

Initialize a perforated wrapper around a single module.

Parameters
  • original_module (nn.Module): Original module being wrapped.
  • name (str): Qualified module name used for save/load mapping.
name
processor_array
processor
layer_array
def simulate_cycles(self, num_cycles, nodeCount):
225    def simulate_cycles(self, num_cycles, nodeCount):
226        """Expand internal layer/processor lists for stored dendrite cycles.
227
228        Parameters
229        ----------
230        num_cycles : int
231            Number of perforation cycles represented in saved state.
232        nodeCount : int
233            Node count hint kept for interface compatibility.
234
235        Returns
236        -------
237        None
238            This function does not return a value.
239        """
240        for i in range(0, num_cycles, 2):
241            self.layer_array.append(copy.deepcopy(self.layer_array[0]))
242            if not self.processor is None:
243                processor = copy.deepcopy(self.processor)
244                processor.pre = self.processor.pre_d
245                processor.post = self.processor.post_d
246                self.processor_array.append(processor)
247            else:
248                self.processor_array.append(None)

Expand internal layer/processor lists for stored dendrite cycles.

Parameters
  • num_cycles (int): Number of perforation cycles represented in saved state.
  • nodeCount (int): Node count hint kept for interface compatibility.
Returns
  • None: This function does not return a value.
def process_and_forward(self, *args2, **kwargs2):
250    def process_and_forward(self, *args2, **kwargs2):
251        """Execute one dendrite layer and write output into shared storage.
252
253        Parameters
254        ----------
255        *args2 : tuple
256            Positional values where first entries are layer index and output
257            buffer, followed by layer inputs.
258        **kwargs2 : dict
259            Keyword arguments forwarded to the wrapped layer.
260
261        Returns
262        -------
263        None
264            This function does not return a value.
265        """
266        c = args2[0]
267        dendrite_outs = args2[1]
268        args2 = args2[2:]
269        if self.processor_array[c] != None:
270            out_values = self.processor_array[c].pre(*args2, **kwargs2)
271        out_values = self.layer_array[c](*args2, **kwargs2)
272        if self.processor_array[c] != None:
273            out = self.processor_array[c].post(out_values)
274        else:
275            out = out_values
276        dendrite_outs[c] = out

Execute one dendrite layer and write output into shared storage.

Parameters
  • *args2 (tuple): Positional values where first entries are layer index and output buffer, followed by layer inputs.
  • **kwargs2 (dict): Keyword arguments forwarded to the wrapped layer.
Returns
  • None: This function does not return a value.
def process_and_pre(self, *args, **kwargs):
278    def process_and_pre(self, *args, **kwargs):
279        """Run the final pre-dendrite layer pass and cache its output.
280
281        Parameters
282        ----------
283        *args : tuple
284            Positional values with output buffer first, then model inputs.
285        **kwargs : dict
286            Keyword arguments forwarded to the wrapped layer.
287
288        Returns
289        -------
290        None
291            This function does not return a value.
292        """
293        dendrite_outs = args[0]
294        args = args[1:]
295        out = self.layer_array[-1].forward(*args, **kwargs)
296        if not self.processor_array[-1] is None:
297            out = self.processor_array[-1].pre(out)
298        dendrite_outs[len(self.layer_array) - 1] = out

Run the final pre-dendrite layer pass and cache its output.

Parameters
  • *args (tuple): Positional values with output buffer first, then model inputs.
  • **kwargs (dict): Keyword arguments forwarded to the wrapped layer.
Returns
  • None: This function does not return a value.
def forward(self, *args, **kwargs):
300    def forward(self, *args, **kwargs):
301        """Run perforated forward pass with dendrite accumulation.
302
303        Parameters
304        ----------
305        *args : tuple
306            Positional arguments forwarded through wrapped layers.
307        **kwargs : dict
308            Keyword arguments forwarded through wrapped layers.
309
310        Returns
311        -------
312        Any
313            Final model output after optional processor post-processing.
314        """
315        # this is currently false anyway, just remove the doing multi idea
316        doing_multi = doing_threading
317        dendrite_outs = [None] * len(self.layer_array)
318        threads = {}
319        for c in range(0, len(self.layer_array) - 1):
320            args2, kwargs2 = args, kwargs
321            if doing_multi:
322                threads[c] = Thread(
323                    target=self.process_and_forward,
324                    args=(c, dendrite_outs, *args),
325                    kwargs=kwargs,
326                )
327            else:
328                self.process_and_forward(c, dendrite_outs, *args2, **kwargs2)
329        if doing_multi:
330            threads[len(self.layer_array) - 1] = Thread(
331                target=self.process_and_pre, args=(dendrite_outs, *args), kwargs=kwargs
332            )
333        else:
334            self.process_and_pre(dendrite_outs, *args, **kwargs)
335        if doing_multi:
336            for i in range(len(dendrite_outs)):
337                threads[i].start()
338            for i in range(len(dendrite_outs)):
339                threads[i].join()
340        for out_index in range(0, len(self.layer_array)):
341            current_out = dendrite_outs[out_index]
342
343            if len(self.layer_array) > 1 and hasattr(self, "skip_weights"):
344                for in_index in range(0, out_index):
345                    # Use out_index - 1 because skip_weights[0] is never used
346                    current_out = (
347                        current_out
348                        + self.skip_weights[out_index - 1][in_index, :]
349                        .reshape(self.view_tuple)
350                        .to(current_out.device)
351                        * dendrite_outs[in_index]
352                    )
353                if out_index < len(self.layer_array) - 1:
354                    current_out = GPA.pc.get_pai_forward_function()(current_out)
355            dendrite_outs[out_index] = current_out
356        if not self.processor_array[-1] is None:
357            current_out = self.processor_array[-1].post(current_out)
358        return current_out

Run perforated forward pass with dendrite accumulation.

Parameters
  • *args (tuple): Positional arguments forwarded through wrapped layers.
  • **kwargs (dict): Keyword arguments forwarded through wrapped layers.
Returns
  • Any: Final model output after optional processor post-processing.
class PAITrackedModule(torch.nn.modules.module.Module):
361class PAITrackedModule(nn.Module):
362    """Wrapper for modules you don't want to add dendrites to. Ensures all modules are accounted for."""
363
364    def __init__(self, start_module, name):
365        """Initialize PAITrackedModule.
366
367        This function sets up the tracked neuron module to wrap the start_module
368        without adding dendrites.
369
370        Parameters
371        ----------
372        start_module : nn.Module
373            The module to wrap.
374        name : str
375            The name of the neuron module.
376        """
377        super(PAITrackedModule, self).__init__()
378
379        if isinstance(start_module, nn.Module):
380            self.main_module = start_module
381        else:
382            print("start_module must be nn.Module: %s" % name)
383            print(type(start_module))
384            print(start_module)
385            sys.exit(-1)
386        self.name = name
387
388        self.type = "tracked_module"
389
390    def __getattr__(self, name):
391        """Get member variables from the main module.
392
393        Parameters
394        ----------
395        name : str
396            The name of the variable to retrieve.
397        Returns
398        -------
399        The requested variable.
400
401        Notes
402        -----
403        This method first attempts to retrieve the attribute from the PAINeuronModule instance.
404        If it fails, it tries to get the attribute from the wrapped main_module.
405        This allows seamless access to the main module's attributes without modifying original code.
406        """
407        try:
408            return super().__getattr__(name)
409        except AttributeError:
410            return getattr(self.main_module, name)
411
412    def forward(self, *args, **kwargs):
413        """Forward pass for tracked layer.
414
415        Parameters
416        ----------
417        *args : tuple
418            Positional arguments for the forward pass.
419        **kwargs : dict
420            Keyword arguments for the forward pass.
421
422        Returns
423        -------
424        Any
425            The output of the module
426
427        Notes
428        -----
429            The output of this forward function will have the same format as the output
430            of the original module
431        """
432        return self.main_module(*args, **kwargs)
433
434    def __str__(self):
435        """String representation of the layer.
436
437        Parameters
438        ----------
439        None
440
441        Returns
442        -------
443        str
444            String representation of the layer.
445
446        Notes
447        -----
448        Setting for verbose changes level of details in the string output.
449        """
450
451        if GPA.pc.get_verbose():
452            total_string = self.main_module.__str__()
453            total_string = "PAITrackedLayer(" + total_string + ")"
454            return total_string
455        else:
456            total_string = self.main_module.__str__()
457            total_string = "PAITrackedLayer(" + total_string + ")"
458            return total_string
459
460    def __repr__(self):
461        """Representation of the layer."""
462        return self.__str__()

Wrapper for modules you don't want to add dendrites to. Ensures all modules are accounted for.

PAITrackedModule(start_module, name)
364    def __init__(self, start_module, name):
365        """Initialize PAITrackedModule.
366
367        This function sets up the tracked neuron module to wrap the start_module
368        without adding dendrites.
369
370        Parameters
371        ----------
372        start_module : nn.Module
373            The module to wrap.
374        name : str
375            The name of the neuron module.
376        """
377        super(PAITrackedModule, self).__init__()
378
379        if isinstance(start_module, nn.Module):
380            self.main_module = start_module
381        else:
382            print("start_module must be nn.Module: %s" % name)
383            print(type(start_module))
384            print(start_module)
385            sys.exit(-1)
386        self.name = name
387
388        self.type = "tracked_module"

Initialize PAITrackedModule.

This function sets up the tracked neuron module to wrap the start_module without adding dendrites.

Parameters
  • start_module (nn.Module): The module to wrap.
  • name (str): The name of the neuron module.
name
def type(self, dst_type: torch.dtype | str) -> Self:
1167    def type(self, dst_type: dtype | str) -> Self:
1168        r"""Casts all parameters and buffers to :attr:`dst_type`.
1169
1170        .. note::
1171            This method modifies the module in-place.
1172
1173        Args:
1174            dst_type (type or string): the desired type
1175
1176        Returns:
1177            Module: self
1178        """
1179        return self._apply(lambda t: t.type(dst_type))

Casts all parameters and buffers to dst_type.

This method modifies the module in-place.

Args: dst_type (type or string): the desired type

Returns: Module: self

def forward(self, *args, **kwargs):
412    def forward(self, *args, **kwargs):
413        """Forward pass for tracked layer.
414
415        Parameters
416        ----------
417        *args : tuple
418            Positional arguments for the forward pass.
419        **kwargs : dict
420            Keyword arguments for the forward pass.
421
422        Returns
423        -------
424        Any
425            The output of the module
426
427        Notes
428        -----
429            The output of this forward function will have the same format as the output
430            of the original module
431        """
432        return self.main_module(*args, **kwargs)

Forward pass for tracked layer.

Parameters
  • *args (tuple): Positional arguments for the forward pass.
  • **kwargs (dict): Keyword arguments for the forward pass.
Returns
  • Any: The output of the module
Notes

The output of this forward function will have the same format as the output of the original module