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 a PAI or plain model from a state dictionary into an unconverted network.
107
108    Parameters
109    ----------
110    net : nn.Module
111        Base network architecture (not yet converted to PerforatedModules).
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    # Find which module paths need PAI wrapping from the state dict
121    pai_module_names = set(
122        key[: -len(".num_cycles")]
123        for key in state_dict.keys()
124        if key.endswith(".num_cycles")
125    )
126
127    # Clean up tracked-module scaffolding: for any key containing .main_module.
128    # where the prefix is not a PAI module, strip .main_module. out so the
129    # weights load directly into the plain module. Also drop any module_id keys.
130    cleaned = {}
131    for key, value in state_dict.items():
132        if ".main_module." in key:
133            prefix = key[: key.index(".main_module.")]
134            if prefix not in pai_module_names:
135                key = key.replace(".main_module.", ".", 1)
136        if "module_id" in key.split("."):
137            continue
138        cleaned[key] = value
139    state_dict = cleaned
140
141    if not pai_module_names:
142        net.load_state_dict(state_dict)
143        return net
144
145    # Wrap each identified module as a PerforatedModule in-place
146    for module_name in pai_module_names:
147        parts = module_name.split(".")
148        if len(parts) == 1:
149            parent = net
150            attr = parts[0]
151        else:
152            parent = net.get_submodule(".".join(parts[:-1]))
153            attr = parts[-1]
154        original_module = getattr(parent, attr)
155        setattr(parent, attr, PerforatedModule(original_module, "." + module_name))
156
157    pai_modules = get_pai_modules(net, 0)
158
159    for module in pai_modules:
160        # Set up name to be what will be saved in the state dict
161        module_name = UPA.get_module_base_name(module)
162        # Then instantiate as many Dendrites as were created during training
163        num_cycles = int(state_dict[module_name + ".num_cycles"].item())
164        # extract node index from state_dict
165        nodeCount = 10
166        # also extract view tuple
167        if num_cycles > 0:
168            module.simulate_cycles(num_cycles, nodeCount)
169        if not module.processor is None:
170            processor = copy.deepcopy(module.processor)
171            processor.pre = module.processor.post_n1
172            processor.post = module.processor.post_n2
173            module.processor_array.append(processor)
174        else:
175            module.processor_array.append(None)
176
177        # Create ParameterList for skip_weights based on num_cycles
178        num_params = num_cycles // 2
179        skip_weights_list = nn.ParameterList()
180        for i in range(num_params):
181            param_key = module_name + f".skip_weights.{i}"
182            if param_key in state_dict:
183                param = nn.Parameter(torch.randn(state_dict[param_key].shape))
184                skip_weights_list.append(param)
185        module.skip_weights = skip_weights_list
186
187        module.register_buffer("view_tuple", state_dict[module_name + ".view_tuple"])
188
189    net.load_state_dict(state_dict)
190
191    for module in pai_modules:
192        temp = tuple(module.view_tuple.tolist())
193        del module.view_tuple
194        module.view_tuple = temp
195
196    return net
197
198
199def load_pai_model(net, filename):
200    """Load a saved PAI model file into a network.
201
202    Parameters
203    ----------
204    net : nn.Module
205        Base network architecture.
206    filename : str
207        Path to a safetensors state file.
208
209    Returns
210    -------
211    nn.Module
212        Loaded network.
213    """
214    state_dict = load_file(filename)
215    return load_pai_model_from_dict(net, state_dict)
216
217
218class PerforatedModule(nn.Module):
219    def __init__(self, original_module, name):
220        """Initialize a perforated wrapper around a single module.
221
222        Parameters
223        ----------
224        original_module : nn.Module
225            Original module being wrapped.
226        name : str
227            Qualified module name used for save/load mapping.
228        """
229        super(PerforatedModule, self).__init__()
230        self.name = name
231        self.register_buffer("node_index", torch.tensor(-1))
232        self.register_buffer("num_cycles", torch.tensor(-1))
233        self.register_buffer("view_tuple", torch.tensor(-1))
234        self.processor_array = []
235        self.processor = None
236        self.layer_array = nn.ModuleList([original_module])
237        # If this original module has processing functions save the processor
238        if type(original_module) in GPA.pc.get_modules_with_processing():
239            module_index = GPA.pc.get_modules_with_processing().index(
240                type(original_module)
241            )
242            self.processor = GPA.pc.get_modules_processing_classes()[module_index]()
243        elif (
244            type(original_module).__name__ in GPA.pc.get_module_names_with_processing()
245        ):
246            module_index = GPA.pc.get_module_names_with_processing().index(
247                type(original_module).__name__
248            )
249            self.processor = GPA.pc.get_module_by_name_processing_classes()[
250                module_index
251            ]()
252
253    def simulate_cycles(self, num_cycles, nodeCount):
254        """Expand internal layer/processor lists for stored dendrite cycles.
255
256        Parameters
257        ----------
258        num_cycles : int
259            Number of perforation cycles represented in saved state.
260        nodeCount : int
261            Node count hint kept for interface compatibility.
262
263        Returns
264        -------
265        None
266            This function does not return a value.
267        """
268        for i in range(0, num_cycles, 2):
269            self.layer_array.append(copy.deepcopy(self.layer_array[0]))
270            if not self.processor is None:
271                processor = copy.deepcopy(self.processor)
272                processor.pre = self.processor.pre_d
273                processor.post = self.processor.post_d
274                self.processor_array.append(processor)
275            else:
276                self.processor_array.append(None)
277
278    def process_and_forward(self, *args2, **kwargs2):
279        """Execute one dendrite layer and write output into shared storage.
280
281        Parameters
282        ----------
283        *args2 : tuple
284            Positional values where first entries are layer index and output
285            buffer, followed by layer inputs.
286        **kwargs2 : dict
287            Keyword arguments forwarded to the wrapped layer.
288
289        Returns
290        -------
291        None
292            This function does not return a value.
293        """
294        c = args2[0]
295        dendrite_outs = args2[1]
296        args2 = args2[2:]
297        if self.processor_array[c] != None:
298            out_values = self.processor_array[c].pre(*args2, **kwargs2)
299        out_values = self.layer_array[c](*args2, **kwargs2)
300        if self.processor_array[c] != None:
301            out = self.processor_array[c].post(out_values)
302        else:
303            out = out_values
304        dendrite_outs[c] = out
305
306    def process_and_pre(self, *args, **kwargs):
307        """Run the final pre-dendrite layer pass and cache its output.
308
309        Parameters
310        ----------
311        *args : tuple
312            Positional values with output buffer first, then model inputs.
313        **kwargs : dict
314            Keyword arguments forwarded to the wrapped layer.
315
316        Returns
317        -------
318        None
319            This function does not return a value.
320        """
321        dendrite_outs = args[0]
322        args = args[1:]
323        out = self.layer_array[-1].forward(*args, **kwargs)
324        if not self.processor_array[-1] is None:
325            out = self.processor_array[-1].pre(out)
326        dendrite_outs[len(self.layer_array) - 1] = out
327
328    def forward(self, *args, **kwargs):
329        """Run perforated forward pass with dendrite accumulation.
330
331        Parameters
332        ----------
333        *args : tuple
334            Positional arguments forwarded through wrapped layers.
335        **kwargs : dict
336            Keyword arguments forwarded through wrapped layers.
337
338        Returns
339        -------
340        Any
341            Final model output after optional processor post-processing.
342        """
343        # this is currently false anyway, just remove the doing multi idea
344        doing_multi = doing_threading
345        dendrite_outs = [None] * len(self.layer_array)
346        threads = {}
347        for c in range(0, len(self.layer_array) - 1):
348            args2, kwargs2 = args, kwargs
349            if doing_multi:
350                threads[c] = Thread(
351                    target=self.process_and_forward,
352                    args=(c, dendrite_outs, *args),
353                    kwargs=kwargs,
354                )
355            else:
356                self.process_and_forward(c, dendrite_outs, *args2, **kwargs2)
357        if doing_multi:
358            threads[len(self.layer_array) - 1] = Thread(
359                target=self.process_and_pre, args=(dendrite_outs, *args), kwargs=kwargs
360            )
361        else:
362            self.process_and_pre(dendrite_outs, *args, **kwargs)
363        if doing_multi:
364            for i in range(len(dendrite_outs)):
365                threads[i].start()
366            for i in range(len(dendrite_outs)):
367                threads[i].join()
368        for out_index in range(0, len(self.layer_array)):
369            current_out = dendrite_outs[out_index]
370
371            if len(self.layer_array) > 1 and hasattr(self, "skip_weights") and len(self.skip_weights) > 0:
372                for in_index in range(0, out_index):
373                    # Use out_index - 1 because skip_weights[0] is never used
374                    current_out = (
375                        current_out
376                        + self.skip_weights[out_index - 1][in_index, :]
377                        .reshape(self.view_tuple)
378                        .to(current_out.device)
379                        * dendrite_outs[in_index]
380                    )
381                if out_index < len(self.layer_array) - 1:
382                    current_out = GPA.pc.get_pai_forward_function()(current_out)
383            dendrite_outs[out_index] = current_out
384        if not self.processor_array[-1] is None:
385            current_out = self.processor_array[-1].post(current_out)
386        return current_out
387
388
389class PAITrackedModule(nn.Module):
390    """Wrapper for modules you don't want to add dendrites to. Ensures all modules are accounted for."""
391
392    def __init__(self, start_module, name):
393        """Initialize PAITrackedModule.
394
395        This function sets up the tracked neuron module to wrap the start_module
396        without adding dendrites.
397
398        Parameters
399        ----------
400        start_module : nn.Module
401            The module to wrap.
402        name : str
403            The name of the neuron module.
404        """
405        super(PAITrackedModule, self).__init__()
406
407        if isinstance(start_module, nn.Module):
408            self.main_module = start_module
409        else:
410            print("start_module must be nn.Module: %s" % name)
411            print(type(start_module))
412            print(start_module)
413            sys.exit(-1)
414        self.name = name
415
416        self.type = "tracked_module"
417
418    def __getattr__(self, name):
419        """Get member variables from the main module.
420
421        Parameters
422        ----------
423        name : str
424            The name of the variable to retrieve.
425        Returns
426        -------
427        The requested variable.
428
429        Notes
430        -----
431        This method first attempts to retrieve the attribute from the PAINeuronModule instance.
432        If it fails, it tries to get the attribute from the wrapped main_module.
433        This allows seamless access to the main module's attributes without modifying original code.
434        """
435        try:
436            return super().__getattr__(name)
437        except AttributeError:
438            return getattr(self.main_module, name)
439
440    def forward(self, *args, **kwargs):
441        """Forward pass for tracked layer.
442
443        Parameters
444        ----------
445        *args : tuple
446            Positional arguments for the forward pass.
447        **kwargs : dict
448            Keyword arguments for the forward pass.
449
450        Returns
451        -------
452        Any
453            The output of the module
454
455        Notes
456        -----
457            The output of this forward function will have the same format as the output
458            of the original module
459        """
460        return self.main_module(*args, **kwargs)
461
462    def __str__(self):
463        """String representation of the layer.
464
465        Parameters
466        ----------
467        None
468
469        Returns
470        -------
471        str
472            String representation of the layer.
473
474        Notes
475        -----
476        Setting for verbose changes level of details in the string output.
477        """
478
479        if GPA.pc.get_verbose():
480            total_string = self.main_module.__str__()
481            total_string = "PAITrackedLayer(" + total_string + ")"
482            return total_string
483        else:
484            total_string = self.main_module.__str__()
485            total_string = "PAITrackedLayer(" + total_string + ")"
486            return total_string
487
488    def __repr__(self):
489        """Representation of the layer."""
490        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 a PAI or plain model from a state dictionary into an unconverted network.
108
109    Parameters
110    ----------
111    net : nn.Module
112        Base network architecture (not yet converted to PerforatedModules).
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    # Find which module paths need PAI wrapping from the state dict
122    pai_module_names = set(
123        key[: -len(".num_cycles")]
124        for key in state_dict.keys()
125        if key.endswith(".num_cycles")
126    )
127
128    # Clean up tracked-module scaffolding: for any key containing .main_module.
129    # where the prefix is not a PAI module, strip .main_module. out so the
130    # weights load directly into the plain module. Also drop any module_id keys.
131    cleaned = {}
132    for key, value in state_dict.items():
133        if ".main_module." in key:
134            prefix = key[: key.index(".main_module.")]
135            if prefix not in pai_module_names:
136                key = key.replace(".main_module.", ".", 1)
137        if "module_id" in key.split("."):
138            continue
139        cleaned[key] = value
140    state_dict = cleaned
141
142    if not pai_module_names:
143        net.load_state_dict(state_dict)
144        return net
145
146    # Wrap each identified module as a PerforatedModule in-place
147    for module_name in pai_module_names:
148        parts = module_name.split(".")
149        if len(parts) == 1:
150            parent = net
151            attr = parts[0]
152        else:
153            parent = net.get_submodule(".".join(parts[:-1]))
154            attr = parts[-1]
155        original_module = getattr(parent, attr)
156        setattr(parent, attr, PerforatedModule(original_module, "." + module_name))
157
158    pai_modules = get_pai_modules(net, 0)
159
160    for module in pai_modules:
161        # Set up name to be what will be saved in the state dict
162        module_name = UPA.get_module_base_name(module)
163        # Then instantiate as many Dendrites as were created during training
164        num_cycles = int(state_dict[module_name + ".num_cycles"].item())
165        # extract node index from state_dict
166        nodeCount = 10
167        # also extract view tuple
168        if num_cycles > 0:
169            module.simulate_cycles(num_cycles, nodeCount)
170        if not module.processor is None:
171            processor = copy.deepcopy(module.processor)
172            processor.pre = module.processor.post_n1
173            processor.post = module.processor.post_n2
174            module.processor_array.append(processor)
175        else:
176            module.processor_array.append(None)
177
178        # Create ParameterList for skip_weights based on num_cycles
179        num_params = num_cycles // 2
180        skip_weights_list = nn.ParameterList()
181        for i in range(num_params):
182            param_key = module_name + f".skip_weights.{i}"
183            if param_key in state_dict:
184                param = nn.Parameter(torch.randn(state_dict[param_key].shape))
185                skip_weights_list.append(param)
186        module.skip_weights = skip_weights_list
187
188        module.register_buffer("view_tuple", state_dict[module_name + ".view_tuple"])
189
190    net.load_state_dict(state_dict)
191
192    for module in pai_modules:
193        temp = tuple(module.view_tuple.tolist())
194        del module.view_tuple
195        module.view_tuple = temp
196
197    return net

Load a PAI or plain model from a state dictionary into an unconverted network.

Parameters
  • net (nn.Module): Base network architecture (not yet converted to PerforatedModules).
  • state_dict (dict): Serialized model state.
Returns
  • nn.Module: Network with loaded state and reconstructed runtime buffers.
def load_pai_model(net, filename):
200def load_pai_model(net, filename):
201    """Load a saved PAI model file into a network.
202
203    Parameters
204    ----------
205    net : nn.Module
206        Base network architecture.
207    filename : str
208        Path to a safetensors state file.
209
210    Returns
211    -------
212    nn.Module
213        Loaded network.
214    """
215    state_dict = load_file(filename)
216    return load_pai_model_from_dict(net, state_dict)

Load a saved PAI model file into a network.

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

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):
254    def simulate_cycles(self, num_cycles, nodeCount):
255        """Expand internal layer/processor lists for stored dendrite cycles.
256
257        Parameters
258        ----------
259        num_cycles : int
260            Number of perforation cycles represented in saved state.
261        nodeCount : int
262            Node count hint kept for interface compatibility.
263
264        Returns
265        -------
266        None
267            This function does not return a value.
268        """
269        for i in range(0, num_cycles, 2):
270            self.layer_array.append(copy.deepcopy(self.layer_array[0]))
271            if not self.processor is None:
272                processor = copy.deepcopy(self.processor)
273                processor.pre = self.processor.pre_d
274                processor.post = self.processor.post_d
275                self.processor_array.append(processor)
276            else:
277                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):
279    def process_and_forward(self, *args2, **kwargs2):
280        """Execute one dendrite layer and write output into shared storage.
281
282        Parameters
283        ----------
284        *args2 : tuple
285            Positional values where first entries are layer index and output
286            buffer, followed by layer inputs.
287        **kwargs2 : dict
288            Keyword arguments forwarded to the wrapped layer.
289
290        Returns
291        -------
292        None
293            This function does not return a value.
294        """
295        c = args2[0]
296        dendrite_outs = args2[1]
297        args2 = args2[2:]
298        if self.processor_array[c] != None:
299            out_values = self.processor_array[c].pre(*args2, **kwargs2)
300        out_values = self.layer_array[c](*args2, **kwargs2)
301        if self.processor_array[c] != None:
302            out = self.processor_array[c].post(out_values)
303        else:
304            out = out_values
305        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):
307    def process_and_pre(self, *args, **kwargs):
308        """Run the final pre-dendrite layer pass and cache its output.
309
310        Parameters
311        ----------
312        *args : tuple
313            Positional values with output buffer first, then model inputs.
314        **kwargs : dict
315            Keyword arguments forwarded to the wrapped layer.
316
317        Returns
318        -------
319        None
320            This function does not return a value.
321        """
322        dendrite_outs = args[0]
323        args = args[1:]
324        out = self.layer_array[-1].forward(*args, **kwargs)
325        if not self.processor_array[-1] is None:
326            out = self.processor_array[-1].pre(out)
327        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):
329    def forward(self, *args, **kwargs):
330        """Run perforated forward pass with dendrite accumulation.
331
332        Parameters
333        ----------
334        *args : tuple
335            Positional arguments forwarded through wrapped layers.
336        **kwargs : dict
337            Keyword arguments forwarded through wrapped layers.
338
339        Returns
340        -------
341        Any
342            Final model output after optional processor post-processing.
343        """
344        # this is currently false anyway, just remove the doing multi idea
345        doing_multi = doing_threading
346        dendrite_outs = [None] * len(self.layer_array)
347        threads = {}
348        for c in range(0, len(self.layer_array) - 1):
349            args2, kwargs2 = args, kwargs
350            if doing_multi:
351                threads[c] = Thread(
352                    target=self.process_and_forward,
353                    args=(c, dendrite_outs, *args),
354                    kwargs=kwargs,
355                )
356            else:
357                self.process_and_forward(c, dendrite_outs, *args2, **kwargs2)
358        if doing_multi:
359            threads[len(self.layer_array) - 1] = Thread(
360                target=self.process_and_pre, args=(dendrite_outs, *args), kwargs=kwargs
361            )
362        else:
363            self.process_and_pre(dendrite_outs, *args, **kwargs)
364        if doing_multi:
365            for i in range(len(dendrite_outs)):
366                threads[i].start()
367            for i in range(len(dendrite_outs)):
368                threads[i].join()
369        for out_index in range(0, len(self.layer_array)):
370            current_out = dendrite_outs[out_index]
371
372            if len(self.layer_array) > 1 and hasattr(self, "skip_weights") and len(self.skip_weights) > 0:
373                for in_index in range(0, out_index):
374                    # Use out_index - 1 because skip_weights[0] is never used
375                    current_out = (
376                        current_out
377                        + self.skip_weights[out_index - 1][in_index, :]
378                        .reshape(self.view_tuple)
379                        .to(current_out.device)
380                        * dendrite_outs[in_index]
381                    )
382                if out_index < len(self.layer_array) - 1:
383                    current_out = GPA.pc.get_pai_forward_function()(current_out)
384            dendrite_outs[out_index] = current_out
385        if not self.processor_array[-1] is None:
386            current_out = self.processor_array[-1].post(current_out)
387        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):
390class PAITrackedModule(nn.Module):
391    """Wrapper for modules you don't want to add dendrites to. Ensures all modules are accounted for."""
392
393    def __init__(self, start_module, name):
394        """Initialize PAITrackedModule.
395
396        This function sets up the tracked neuron module to wrap the start_module
397        without adding dendrites.
398
399        Parameters
400        ----------
401        start_module : nn.Module
402            The module to wrap.
403        name : str
404            The name of the neuron module.
405        """
406        super(PAITrackedModule, self).__init__()
407
408        if isinstance(start_module, nn.Module):
409            self.main_module = start_module
410        else:
411            print("start_module must be nn.Module: %s" % name)
412            print(type(start_module))
413            print(start_module)
414            sys.exit(-1)
415        self.name = name
416
417        self.type = "tracked_module"
418
419    def __getattr__(self, name):
420        """Get member variables from the main module.
421
422        Parameters
423        ----------
424        name : str
425            The name of the variable to retrieve.
426        Returns
427        -------
428        The requested variable.
429
430        Notes
431        -----
432        This method first attempts to retrieve the attribute from the PAINeuronModule instance.
433        If it fails, it tries to get the attribute from the wrapped main_module.
434        This allows seamless access to the main module's attributes without modifying original code.
435        """
436        try:
437            return super().__getattr__(name)
438        except AttributeError:
439            return getattr(self.main_module, name)
440
441    def forward(self, *args, **kwargs):
442        """Forward pass for tracked layer.
443
444        Parameters
445        ----------
446        *args : tuple
447            Positional arguments for the forward pass.
448        **kwargs : dict
449            Keyword arguments for the forward pass.
450
451        Returns
452        -------
453        Any
454            The output of the module
455
456        Notes
457        -----
458            The output of this forward function will have the same format as the output
459            of the original module
460        """
461        return self.main_module(*args, **kwargs)
462
463    def __str__(self):
464        """String representation of the layer.
465
466        Parameters
467        ----------
468        None
469
470        Returns
471        -------
472        str
473            String representation of the layer.
474
475        Notes
476        -----
477        Setting for verbose changes level of details in the string output.
478        """
479
480        if GPA.pc.get_verbose():
481            total_string = self.main_module.__str__()
482            total_string = "PAITrackedLayer(" + total_string + ")"
483            return total_string
484        else:
485            total_string = self.main_module.__str__()
486            total_string = "PAITrackedLayer(" + total_string + ")"
487            return total_string
488
489    def __repr__(self):
490        """Representation of the layer."""
491        return self.__str__()

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

PAITrackedModule(start_module, name)
393    def __init__(self, start_module, name):
394        """Initialize PAITrackedModule.
395
396        This function sets up the tracked neuron module to wrap the start_module
397        without adding dendrites.
398
399        Parameters
400        ----------
401        start_module : nn.Module
402            The module to wrap.
403        name : str
404            The name of the neuron module.
405        """
406        super(PAITrackedModule, self).__init__()
407
408        if isinstance(start_module, nn.Module):
409            self.main_module = start_module
410        else:
411            print("start_module must be nn.Module: %s" % name)
412            print(type(start_module))
413            print(start_module)
414            sys.exit(-1)
415        self.name = name
416
417        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):
441    def forward(self, *args, **kwargs):
442        """Forward pass for tracked layer.
443
444        Parameters
445        ----------
446        *args : tuple
447            Positional arguments for the forward pass.
448        **kwargs : dict
449            Keyword arguments for the forward pass.
450
451        Returns
452        -------
453        Any
454            The output of the module
455
456        Notes
457        -----
458            The output of this forward function will have the same format as the output
459            of the original module
460        """
461        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