perforatedai.modules_perforatedai

   1# Copyright (c) 2025 Perforated AI
   2
   3import copy
   4import math
   5import os
   6import pdb
   7import sys
   8import time
   9from datetime import datetime
  10
  11import numpy as np
  12import torch
  13import torch.nn as nn
  14import traceback
  15
  16from perforatedai import globals_perforatedai as GPA
  17from perforatedai import utils_perforatedai as UPA
  18
  19try:
  20    from perforatedbp import modules_pbp as MPB
  21except ModuleNotFoundError as e:
  22    # Only pass if perforatedbp package itself is missing
  23    if e.name == "perforatedbp":
  24        pass
  25    else:
  26        # perforatedbp exists but is missing a dependency
  27        raise
  28
  29
  30# Values for Dendrite training, minimally used in open source version
  31_DENDRITE_TENSOR_VALUES_BASE = [
  32    "shape"
  33]  # Shape is tensor of same shape as total neurons in module
  34_DENDRITE_SINGLE_VALUES_BASE = []
  35
  36DENDRITE_INIT_VALUES = ["initialized", "current_d_init"]
  37
  38_VALUE_TRACKER_ARRAYS_BASE = ["dendrite_outs"]
  39
  40# Cached values to avoid recomputation (each tracks its own state)
  41_cached_dendrite_tensor_values = None
  42_cached_dendrite_tensor_pb_state = None
  43_cached_dendrite_single_values = None
  44_cached_dendrite_single_pb_state = None
  45_cached_value_tracker_arrays = None
  46_cached_value_tracker_pb_state = None
  47
  48
  49def get_DENDRITE_TENSOR_VALUES():
  50    """Get DENDRITE_TENSOR_VALUES, updating from MPB if perforated_backpropagation is enabled."""
  51    global _cached_dendrite_tensor_values, _cached_dendrite_tensor_pb_state
  52    current_pb_state = GPA.pc.get_perforated_backpropagation()
  53
  54    if (
  55        _cached_dendrite_tensor_values is None
  56        or _cached_dendrite_tensor_pb_state != current_pb_state
  57    ):
  58        _cached_dendrite_tensor_pb_state = current_pb_state
  59        if current_pb_state:
  60            _cached_dendrite_tensor_values = MPB.update_dendrite_tensor_values(
  61                _DENDRITE_TENSOR_VALUES_BASE.copy()
  62            )
  63        else:
  64            _cached_dendrite_tensor_values = _DENDRITE_TENSOR_VALUES_BASE.copy()
  65
  66    return _cached_dendrite_tensor_values
  67
  68
  69def get_DENDRITE_SINGLE_VALUES():
  70    """Get DENDRITE_SINGLE_VALUES, updating from MPB if perforated_backpropagation is enabled."""
  71    global _cached_dendrite_single_values, _cached_dendrite_single_pb_state
  72    current_pb_state = GPA.pc.get_perforated_backpropagation()
  73
  74    if (
  75        _cached_dendrite_single_values is None
  76        or _cached_dendrite_single_pb_state != current_pb_state
  77    ):
  78        _cached_dendrite_single_pb_state = current_pb_state
  79        if current_pb_state:
  80            _cached_dendrite_single_values = MPB.update_dendrite_single_values(
  81                _DENDRITE_SINGLE_VALUES_BASE.copy()
  82            )
  83        else:
  84            _cached_dendrite_single_values = _DENDRITE_SINGLE_VALUES_BASE.copy()
  85
  86    return _cached_dendrite_single_values
  87
  88
  89def get_VALUE_TRACKER_ARRAYS():
  90    """Get VALUE_TRACKER_ARRAYS, updating from MPB if perforated_backpropagation is enabled."""
  91    global _cached_value_tracker_arrays, _cached_value_tracker_pb_state
  92    current_pb_state = GPA.pc.get_perforated_backpropagation()
  93
  94    if (
  95        _cached_value_tracker_arrays is None
  96        or _cached_value_tracker_pb_state != current_pb_state
  97    ):
  98        _cached_value_tracker_pb_state = current_pb_state
  99        if current_pb_state:
 100            _cached_value_tracker_arrays = MPB.update_value_tracker_arrays(
 101                _VALUE_TRACKER_ARRAYS_BASE.copy()
 102            )
 103        else:
 104            _cached_value_tracker_arrays = _VALUE_TRACKER_ARRAYS_BASE.copy()
 105
 106    return _cached_value_tracker_arrays
 107
 108
 109def get_DENDRITE_REINIT_VALUES():
 110    """Get DENDRITE_REINIT_VALUES."""
 111    return get_DENDRITE_TENSOR_VALUES() + get_DENDRITE_SINGLE_VALUES()
 112
 113
 114def get_DENDRITE_SAVE_VALUES():
 115    """Get DENDRITE_SAVE_VALUES."""
 116    return (
 117        get_DENDRITE_TENSOR_VALUES()
 118        + get_DENDRITE_SINGLE_VALUES()
 119        + DENDRITE_INIT_VALUES
 120    )
 121
 122
 123def filter_backward(grad_out, values):
 124    """Filter backward pass for gradient processing.
 125
 126    This function processes gradients during the backward pass,
 127    ensuring correct input dimensions,and applying perforated backpropagation if enabled.
 128
 129    Parameters
 130    ----------
 131    grad_out : torch.Tensor
 132        The gradient output tensor from the backward pass.
 133    values : DendriteValueTracker
 134        A DendriteValueTracker instance containing values associated with the module being processed.
 135
 136    Returns
 137    -------
 138    None
 139    """
 140    if GPA.pc.get_extra_verbose():
 141        print(f"{values[0].layer_name} calling backward")
 142
 143    with torch.no_grad():
 144        val = grad_out.detach()
 145        # If the input dimensions are not initialized
 146        if not values[0].current_d_init.item():
 147            # If input dimensions and gradient don't have same shape trigger error and quit
 148            if len(values[0].this_output_dimensions) != len(grad_out.shape):
 149                print(
 150                    "The following module has not properly set this_output_dimensions"
 151                )
 152                print(values[0].layer_name)
 153                print("it is expecting:")
 154                print(values[0].this_output_dimensions)
 155                print("but received")
 156                print(grad_out.shape)
 157                print(
 158                    "to check these all at once set GPA.pc.set_debugging_output_dimensions(1)"
 159                )
 160                print(
 161                    f"Call MODEL_VARIABLE{values[0].layer_name}.set_this_output_dimensions([...]) on this module after perforate_model"
 162                )
 163                print(
 164                    "where the ... is replaced with the correct vector as described in section 4 of customization.md"
 165                )
 166                if not GPA.pc.get_debugging_output_dimensions():
 167                    sys.exit(0)
 168                else:
 169                    GPA.pc.set_debugging_output_dimensions(2)
 170                    return
 171            # Make sure that the input dimensions are correct
 172            for i in range(len(values[0].this_output_dimensions)):
 173                if values[0].this_output_dimensions[i] == 0:
 174                    continue
 175                # Make sure all input dimensions are either -1 (new format) or exact values (old format)
 176                if (
 177                    not (grad_out.shape[i] == values[0].this_output_dimensions[i])
 178                    and not values[0].this_output_dimensions[i] == -1
 179                ):
 180                    print(
 181                        "The following module has not properly set this_output_dimensions with this incorrect shape"
 182                    )
 183                    print(values[0].layer_name)
 184                    print("it is expecting:")
 185                    print(values[0].this_output_dimensions)
 186                    print("but received")
 187                    print(grad_out.shape)
 188                    print(
 189                        "to check these all at once set GPA.pc.set_debugging_output_dimensions(1)"
 190                    )
 191                    if not GPA.pc.get_debugging_output_dimensions():
 192                        sys.exit(0)
 193                    else:
 194                        GPA.pc.set_debugging_output_dimensions(2)
 195                        return
 196            # Setup the arrays with the now known shape
 197            with torch.no_grad():
 198                if GPA.pc.get_verbose():
 199                    print("setting d shape for")
 200                    print(values[0].layer_name)
 201                    print(val.size())
 202
 203                values[0].set_out_channels(val.size())
 204                values[0].setup_arrays(values[0].out_channels)
 205            # Flag that it has been setup
 206            values[0].current_d_init[0] = 1
 207        if GPA.pc.get_perforated_backpropagation():
 208            MPB.filter_backward_pb(val, values)
 209
 210
 211def set_wrapped_params(model):
 212    """Set parameters as wrapped with dendrites.
 213
 214    Parameters
 215    ----------
 216    model : torch.nn.Module
 217        The model whose parameters are to be marked as wrapped.
 218
 219    Returns
 220    -------
 221    None
 222
 223    """
 224    for param in model.parameters():
 225        param.wrapped = True
 226
 227
 228def set_tracked_params(model):
 229    """Set parameters as tracked without dendrites.
 230
 231    Parameters
 232    ----------
 233    model : torch.nn.Module
 234        The model whose parameters are to be marked as tracked.
 235
 236    Returns
 237    -------
 238    None
 239    """
 240    for param in model.parameters():
 241        param.tracked = True
 242
 243
 244class PAINeuronModule(nn.Module):
 245    """Wrapper to set a module as one that will have dendritic copies."""
 246
 247    def __init__(self, start_module, name):
 248        """Initialize PAINeuronModule.
 249
 250        This function sets up the neuron module to wrap the start_module
 251        and manage its dendritic connections.
 252
 253        Parameters
 254        ----------
 255        start_module : nn.Module
 256            The module to wrap.
 257        name : str
 258            The name of the neuron module.
 259        """
 260        super(PAINeuronModule, self).__init__()
 261
 262        if isinstance(start_module, nn.Module):
 263            self.main_module = start_module
 264        else:
 265            print("start_module must be nn.Module: %s" % name)
 266            print(type(start_module))
 267            print(start_module)
 268            sys.exit(-1)
 269        self.name = name
 270        # Per-module config: loads custom settings from {save_name}_config.json if present.
 271        # Passes both the instance name (id) and the module type so load_config can
 272        # fall back to type-level settings when no name-specific entry exists.
 273        _module_type_name = type(start_module).__name__
 274        self.module_config = GPA.PAIConfig(
 275            module_name=self.name, module_type=_module_type_name
 276        )
 277
 278        set_wrapped_params(self.main_module)
 279        if self.module_config.get_verbose():
 280            print(
 281                f"initing a module {self.name} with main type {type(self.main_module)}"
 282            )
 283            print(start_module)
 284
 285        # If this main_module is one that requires processing set the processor
 286        if type(self.main_module) in self.module_config.get_modules_with_processing():
 287            module_index = self.module_config.get_modules_with_processing().index(
 288                type(self.main_module)
 289            )
 290            self.processor = self.module_config.get_modules_processing_classes()[
 291                module_index
 292            ]()
 293            if self.module_config.get_verbose():
 294                print("with processor")
 295                print(self.processor)
 296        elif (
 297            type(self.main_module).__name__
 298            in self.module_config.get_module_names_with_processing()
 299        ):
 300            module_index = self.module_config.get_module_names_with_processing().index(
 301                type(self.main_module).__name__
 302            )
 303            self.processor = self.module_config.get_module_by_name_processing_classes()[
 304                module_index
 305            ]()
 306            if self.module_config.get_verbose():
 307                print("with processor")
 308                print(self.processor)
 309        else:
 310            self.processor = None
 311
 312        # Field that can be filled in if your activation function requires a parameter
 313        self.activation_function_value = -1
 314        self.type = "neuron_module"
 315
 316        self.register_buffer(
 317            "this_output_dimensions",
 318            (torch.tensor(self.module_config.get_output_dimensions())),
 319        )
 320        if (self.this_output_dimensions == 0).sum() != 1:
 321            print(f"5 Need exactly one 0 in the input dimensions: {self.name}")
 322            print(self.this_output_dimensions)
 323            sys.exit(-1)
 324        self.register_buffer(
 325            "this_node_index",
 326            torch.tensor(self.module_config.get_output_dimensions().index(0)),
 327        )
 328        self.dendrite_modules_added = 0
 329
 330        # Values for dendrite to neuron weights
 331        self.dendrites_to_top = nn.ParameterList()
 332        self.register_parameter("newest_dendrite_to_top", None)
 333        self.candidate_to_top = nn.ParameterList()
 334        self.register_parameter("current_candidate_to_top", None)
 335        # Create the dendrite module
 336        self.dendrite_module = PAIDendriteModule(
 337            self.main_module,
 338            activation_function_value=self.activation_function_value,
 339            name=self.name,
 340            output_dimensions=self.this_output_dimensions,
 341        )
 342        print(self.this_output_dimensions[2:])
 343        print(type(start_module))
 344        # If it is linear and default has convolutional dimensions, automatically set to just be batch size and neuron indexes
 345        if (
 346            issubclass(type(start_module), nn.Linear)
 347            or (
 348                issubclass(type(start_module), GPA.PAISequential)
 349                and issubclass(type(start_module.model[0]), nn.Linear)
 350            )
 351        ) and (
 352            np.array(self.this_output_dimensions)[2:] == -1
 353        ).all():  # Everything past 2 is a negative 1
 354            self.set_this_output_dimensions(self.this_output_dimensions[0:2])
 355        if (
 356            issubclass(type(start_module), nn.Conv1d)
 357            or (
 358                issubclass(type(start_module), GPA.PAISequential)
 359                and issubclass(type(start_module.model[0]), nn.Conv1d)
 360            )
 361        ) and (
 362            np.array(self.this_output_dimensions)[3:] == -1
 363        ).all():  # Everything past 2 is a negative 1
 364            self.set_this_output_dimensions(self.this_output_dimensions[0:3])
 365        # Apply per-module output_dimensions override from config if present
 366        _custom_dims = self.module_config.__dict__.get("_output_dimensions")
 367        if _custom_dims is not None:
 368            self.set_this_output_dimensions(torch.tensor(_custom_dims))
 369        GPA.pai_tracker.add_pai_neuron_module(self)
 370        if self.module_config.get_perforated_backpropagation():
 371            MPB.set_neuron_parameters(self.main_module)
 372
 373    def __getattr__(self, name):
 374        """Get member variables from the main module.
 375
 376        Parameters
 377        ----------
 378        name : str
 379            The name of the variable to retrieve.
 380        Returns
 381        -------
 382        The requested variable.
 383
 384        Notes
 385        -----
 386        This method first attempts to retrieve the attribute from the PAINeuronModule instance.
 387        If it fails, it tries to get the attribute from the wrapped main_module.
 388        This allows seamless access to the main module's attributes without modifying original code.
 389        """
 390        try:
 391            return super().__getattr__(name)
 392        except AttributeError:
 393            return getattr(self.main_module, name)
 394
 395    def __getitem__(self, index):
 396        """Support indexing operations on the main module.
 397
 398        Parameters
 399        ----------
 400        index : int or slice
 401            The index or slice to retrieve.
 402
 403        Returns
 404        -------
 405        The indexed item from the main module.
 406        """
 407        return self.main_module[index]
 408
 409    def apply_pb_grads(self):
 410        """Apply perforated backpropagation gradients if enabled."""
 411        self.dendrite_module.apply_pb_grads()
 412
 413    def apply_pb_zero(self):
 414        """Clear leftover saved tensors if there are any."""
 415        self.dendrite_module.apply_pb_zero()
 416
 417    def clear_processors(self):
 418        """Clear processors if they save values for DeepCopy and save.
 419
 420        Parameters
 421        ----------
 422        None
 423
 424        Returns
 425        -------
 426        None
 427        """
 428
 429        if not self.processor:
 430            return
 431        else:
 432            self.processor.clear_processor()
 433            self.dendrite_module.clear_processors()
 434
 435    def clear_dendrites(self):
 436        """Clear and reset dendrites before loading from a state dict.
 437
 438        Parameters
 439        ----------
 440        None
 441
 442        Returns
 443        -------
 444        None
 445
 446        """
 447        self.dendrite_modules_added = 0
 448        self.dendrites_to_top = nn.ParameterList()
 449        self.candidate_to_top = nn.ParameterList()
 450        self.dendrite_module = PAIDendriteModule(
 451            self.main_module,
 452            activation_function_value=self.activation_function_value,
 453            name=self.name,
 454            output_dimensions=self.this_output_dimensions,
 455        )
 456
 457    def __str__(self):
 458        """String representation of the module.
 459
 460        Parameters
 461        ----------
 462        None
 463
 464        Returns
 465        -------
 466        str
 467            String representation of the module.
 468
 469        Notes
 470        -----
 471        Setting for verbose changes level of details in the string output.
 472        """
 473        # If verbose print the whole module otherwise just print the module type as a PAIModule
 474        if self.module_config.get_verbose():
 475            total_string = self.main_module.__str__()
 476            total_string = "PAIModule(" + total_string + ")"
 477            return total_string + self.dendrite_module.__str__()
 478        else:
 479            total_string = self.main_module.__str__()
 480            total_string = "PAIModule(" + total_string + ")"
 481            return total_string
 482
 483    def __repr__(self):
 484        """Representation of the module."""
 485        return self.__str__()
 486
 487    def set_this_output_dimensions(self, new_output_dimensions):
 488        """Set the input dimensions for the neuron and dendrite blocks.
 489
 490        Signals to this NeuronModule that its input dimensions are different
 491        than the global default.
 492
 493        Parameters
 494        ----------
 495        new_output_dimensions : list
 496            A list or tensor specifying the new input dimensions.
 497        Returns
 498        -------
 499        None
 500
 501        """
 502        if type(new_output_dimensions) is list:
 503            new_output_dimensions = torch.tensor(new_output_dimensions)
 504        delattr(self, "this_output_dimensions")
 505        self.register_buffer(
 506            "this_output_dimensions", new_output_dimensions.detach().clone()
 507        )
 508        if (new_output_dimensions == 0).sum() != 1:
 509            print(f"6 need exactly one 0 in the input dimensions: {self.name}")
 510            print(new_output_dimensions)
 511        self.this_node_index.copy_(
 512            (new_output_dimensions == 0).nonzero(as_tuple=True)[0][0]
 513        )
 514        self.dendrite_module.set_this_output_dimensions(new_output_dimensions)
 515
 516    def set_mode(self, mode):
 517        """Switch between neuron training and dendrite training.
 518
 519        Parameters
 520        ----------
 521        mode : str
 522            The mode to set. Either "n" for neuron training or "p" for pai-dendrite training.
 523
 524        Returns
 525        -------
 526        bool
 527            True if mode was set successfully, False otherwise.
 528
 529        Notes
 530        -----
 531        If False is returned, the mode was not changed due to an error.
 532        This is a problem that should not be ignored, but it can be ignored
 533        by calling PGA.pc.set_checked_skipped_modules(True)
 534        """
 535
 536        if self.module_config.get_verbose():
 537            print(f"{self.name} calling set mode {mode}")
 538        # If returning to neuron training
 539        if mode == "n":
 540            self.dendrite_module.set_mode(mode)
 541            # Initialize the dendrite to neuron connections
 542            if self.dendrite_modules_added > 0:
 543                if self.module_config.get_learn_dendrites_live():
 544                    values = torch.cat(
 545                        (
 546                            self.dendrites_to_top[self.dendrite_modules_added - 1],
 547                            nn.Parameter(
 548                                self.candidate_to_top.detach()
 549                                .clone()
 550                                .to(dtype=self.module_config.get_d_type())
 551                            ),
 552                        ),
 553                        0,
 554                    )
 555                else:
 556                    values = torch.cat(
 557                        (
 558                            self.dendrites_to_top[self.dendrite_modules_added - 1],
 559                            nn.Parameter(
 560                                torch.zeros(
 561                                    (1, self.out_channels),
 562                                    device=self.dendrites_to_top[
 563                                        self.dendrite_modules_added - 1
 564                                    ].device,
 565                                    dtype=self.module_config.get_d_type(),
 566                                )
 567                            ),
 568                        ),
 569                        0,
 570                    )
 571                self.dendrites_to_top.append(
 572                    nn.Parameter(
 573                        values.detach()
 574                        .clone()
 575                        .to(
 576                            device=self.module_config.get_device(),
 577                            dtype=self.module_config.get_d_type(),
 578                        ),
 579                        requires_grad=True,
 580                    )
 581                )
 582            else:
 583                if self.module_config.get_learn_dendrites_live():
 584                    self.dendrites_to_top.append(
 585                        nn.Parameter(
 586                            self.candidate_to_top.detach()
 587                            .clone()
 588                            .to(dtype=self.module_config.get_d_type()),
 589                            requires_grad=True,
 590                        )
 591                    )
 592                else:
 593                    self.dendrites_to_top.append(
 594                        nn.Parameter(
 595                            torch.zeros(
 596                                (1, self.out_channels),
 597                                device=self.module_config.get_device(),
 598                                dtype=self.module_config.get_d_type(),
 599                            )
 600                            .detach()
 601                            .clone(),
 602                            requires_grad=True,
 603                        )
 604                    )
 605            self.dendrite_modules_added += 1
 606            if self.module_config.get_perforated_backpropagation():
 607                MPB.set_module_n_pb(self)
 608                MPB.set_neuron_parameters(self.dendrites_to_top)
 609
 610        # If starting dendrite training
 611        else:
 612            try:
 613                # Save the values that were calculated in filter_backward
 614                self.out_channels = self.dendrite_module.dendrite_values[0].out_channels
 615                self.dendrite_module.out_channels = (
 616                    self.dendrite_module.dendrite_values[0].out_channels
 617                )
 618            except Exception as e:
 619                print(e)
 620                print(
 621                    f"this occurred in module: {self.dendrite_module.dendrite_values[0].layer_name}"
 622                )
 623                print(
 624                    "Module should be added to module_names_to_track so it doesn't have dendrites added"
 625                )
 626                print("If you are getting here but out_channels has not been set")
 627                print(
 628                    "A common reason is that this module never had gradients flow through it."
 629                )
 630                print("I have seen this happen because:")
 631                print("-The weights were frozen (requires_grad = False)")
 632                print(
 633                    "-A model is added but not used so it was converted to a perforated module initialized"
 634                )
 635                print(
 636                    "-A module was converted that doesn't have weights that get modified so backward doesn't flow through it"
 637                )
 638                print(
 639                    "If this is normal behavior set GPA.pc.set_checked_skipped_modules(True) in the main to ignore"
 640                )
 641                print(
 642                    "You can also set right now in this pdb terminal to have this not happen more after checking all modules this cycle."
 643                )
 644                if not self.module_config.get_checked_skipped_modules():
 645                    pdb.set_trace()
 646                return False
 647            # Only change mode if it makes it past the above exception
 648            self.dendrite_module.set_mode(mode)
 649            if self.module_config.get_perforated_backpropagation():
 650                MPB.set_module_p_pb(self)
 651        return True
 652
 653    def create_new_dendrite_module(self):
 654        """Add an additional dendrite module.
 655
 656        Parameters
 657        ----------
 658        None
 659
 660        Returns
 661        -------
 662        None
 663        """
 664        self.dendrite_module.create_new_dendrite_module(self.main_module)
 665
 666    def forward(self, *args, **kwargs):
 667        """Forward pass through the neuron module.
 668
 669        Parameters
 670        ----------
 671        *args : tuple
 672            Positional arguments for the forward pass.
 673        **kwargs : dict
 674            Keyword arguments for the forward pass.
 675
 676        Returns
 677        -------
 678        Any
 679            The output of the module after processing through the neuron and dendrite modules.
 680
 681        Notes
 682        -----
 683            The output of this forward function will have the same format as the output
 684            of the original module
 685        """
 686
 687        # If debugging all input dimensions, quit program on first forward call
 688        if self.module_config.get_debugging_output_dimensions() == 2:
 689            print("all input dim problems now printed")
 690            sys.exit(0)
 691        if self.module_config.get_extra_verbose():
 692            print(f"{self.name} calling forward")
 693        # Call the main modules forward
 694        out = self.main_module(*args, **kwargs)
 695        # Filter with the processor if required
 696        if self.processor is not None:
 697            try:
 698                out = self.processor.post_n1(out)
 699            except Exception as e:
 700                traceback.print_exc(limit=None, chain=True)
 701                print(f"Your post_n1 processor for {self.name} caused this error")
 702                print(
 703                    f"You must check how this is defined and ensure that it is properly"
 704                )
 705                print(f"accepting outputs from the neuron module and returning the")
 706                print(f"single tensor to be combined with the dendrites output tensor")
 707                sys.exit()
 708        # Call the forwards for all of the Dendrites
 709        (
 710            dendrite_outs,
 711            candidate_outs,
 712            candidate_nonlinear_outs,
 713            candidate_outs_non_zeroed,
 714        ) = self.dendrite_module(*args, **kwargs)
 715        # If there are dendrites add all of their outputs to the neurons output
 716        if self.dendrite_modules_added > 0:
 717            for i in range(0, self.dendrite_modules_added):
 718                to_top = self.dendrites_to_top[self.dendrite_modules_added - 1][i, :]
 719                for dim in range(len(dendrite_outs[i].shape)):
 720                    if dim == self.this_node_index:
 721                        continue
 722                    to_top = to_top.unsqueeze(dim)
 723                if self.module_config.get_confirm_correct_sizes():
 724                    to_top = to_top.expand(
 725                        list(dendrite_outs[i].size())[0 : self.this_node_index]
 726                        + [self.out_channels]
 727                        + list(dendrite_outs[i].size())[self.this_node_index + 1 :]
 728                    )
 729                out = out + (dendrite_outs[i].to(out.device) * to_top.to(out.device))
 730
 731        # If learning live, add the candidate's output to the neuron's output via the live weight
 732        if self.module_config.get_perforated_backpropagation():
 733            out = MPB.apply_live_candidate_to_output(
 734                self, out, candidate_nonlinear_outs
 735            )
 736
 737        # Catch if processors are required
 738        if type(out) is tuple:
 739            print(self)
 740            print(
 741                f"The output of the above module {self.name} is a tuple when it must be a single tensor"
 742            )
 743            print(
 744                "This must be fixed to enable the dendrite and neuron output to be combined"
 745            )
 746            print(
 747                "Look in the API customization.md at section 2.2 regarding processors to fix this."
 748            )
 749            pdb.set_trace()
 750
 751        # Call filter backward to ensure the neuron index is setup correctly
 752        if out.requires_grad:
 753            out.register_hook(
 754                lambda grad: filter_backward(grad, self.dendrite_module.dendrite_values)
 755            )
 756
 757        # If there is a processor apply the second neuron stage
 758        if self.processor is not None:
 759            try:
 760                out = self.processor.post_n2(out)
 761            except Exception as e:
 762                traceback.print_exc(limit=None, chain=True)
 763                print(f"Your post_n2 processor for {self.name} caused this error")
 764                print(
 765                    f"You must check how this is defined and ensure that it is properly"
 766                )
 767                print(
 768                    f"accepting the output tensor after combining the neuron's output "
 769                )
 770                print(f"with the dendrite's output and returning something that is the")
 771                print(f"same format as your original module's return")
 772                sys.exit()
 773        return out
 774
 775
 776class TrackedNeuronModule(nn.Module):
 777    """Wrapper for modules you don't want to add dendrites to. Ensures all modules are accounted for."""
 778
 779    def __init__(self, start_module, name):
 780        """Initialize TrackedNeuronModule.
 781
 782        This function sets up the tracked neuron module to wrap the start_module
 783        without adding dendrites.
 784
 785        Parameters
 786        ----------
 787        start_module : nn.Module
 788            The module to wrap.
 789        name : str
 790            The name of the neuron module.
 791        """
 792        super(TrackedNeuronModule, self).__init__()
 793
 794        if isinstance(start_module, nn.Module):
 795            self.main_module = start_module
 796        else:
 797            print("start_module must be nn.Module: %s" % name)
 798            print(type(start_module))
 799            print(start_module)
 800            sys.exit(-1)
 801        self.name = name
 802
 803        self.type = "tracked_module"
 804        set_tracked_params(self.main_module)
 805        if GPA.pc.get_verbose():
 806            print(
 807                f"tracking a module {self.name} with main type {type(self.main_module)}"
 808            )
 809            print(start_module)
 810        GPA.pai_tracker.add_tracked_neuron_module(self)
 811        if GPA.pc.get_perforated_backpropagation():
 812            MPB.set_neuron_parameters(self.main_module)
 813
 814    def __getattr__(self, name):
 815        """Get member variables from the main module.
 816
 817        Parameters
 818        ----------
 819        name : str
 820            The name of the variable to retrieve.
 821        Returns
 822        -------
 823        The requested variable.
 824
 825        Notes
 826        -----
 827        This method first attempts to retrieve the attribute from the PAINeuronModule instance.
 828        If it fails, it tries to get the attribute from the wrapped main_module.
 829        This allows seamless access to the main module's attributes without modifying original code.
 830        """
 831        try:
 832            return super().__getattr__(name)
 833        except AttributeError:
 834            return getattr(self.main_module, name)
 835
 836    def __getitem__(self, index):
 837        """Support indexing operations on the main module.
 838
 839        Parameters
 840        ----------
 841        index : int or slice
 842            The index or slice to retrieve.
 843
 844        Returns
 845        -------
 846        The indexed item from the main module.
 847        """
 848        return self.main_module[index]
 849
 850    def set_mode(self, mode):
 851        """Set mode for tracked module.
 852
 853        Parameters
 854        ----------
 855        mode : str
 856            The mode to set. Either "n" for neuron training or "p" for pai-dendrite training.
 857
 858        Returns
 859        -------
 860        bool
 861            True.
 862
 863        Notes
 864        -----
 865        This function does not change any behavior since this is a tracked module.
 866        """
 867
 868        if GPA.pc.get_verbose():
 869            print(f"{self.name} calling set mode {mode}")
 870        return True
 871
 872    def forward(self, *args, **kwargs):
 873        """Forward pass for tracked module.
 874
 875        Parameters
 876        ----------
 877        *args : tuple
 878            Positional arguments for the forward pass.
 879        **kwargs : dict
 880            Keyword arguments for the forward pass.
 881
 882        Returns
 883        -------
 884        Any
 885            The output of the module
 886
 887        Notes
 888        -----
 889            The output of this forward function will have the same format as the output
 890            of the original module
 891        """
 892        return self.main_module(*args, **kwargs)
 893
 894    def __str__(self):
 895        """String representation of the module.
 896
 897        Parameters
 898        ----------
 899        None
 900
 901        Returns
 902        -------
 903        str
 904            String representation of the module.
 905
 906        Notes
 907        -----
 908        Setting for verbose changes level of details in the string output.
 909        """
 910
 911        if GPA.pc.get_verbose():
 912            total_string = self.main_module.__str__()
 913            total_string = "PAITrackedModule(" + total_string + ")"
 914            return total_string
 915        else:
 916            total_string = self.main_module.__str__()
 917            total_string = "PAITrackedModule(" + total_string + ")"
 918            return total_string
 919
 920    def __repr__(self):
 921        """Representation of the module."""
 922        return self.__str__()
 923
 924
 925def init_params(module, neuron_main_module):
 926    """Randomize weights after duplicating the main module for the next set of dendrites.
 927
 928    Parameters
 929    ----------
 930    module : nn.Module
 931        The new dendrite module to initialize.
 932    neuron_main_module : nn.Module
 933        The main module of the neuron for potential weight scaling.
 934
 935    """
 936    for param in module.parameters():
 937        if param.dtype == torch.uint8:
 938            param.data = torch.randint(0, 256, param.size(), dtype=torch.uint8)
 939        else:
 940            # If factoring in the main modules weights multiply the randn()
 941            #  by the average abs value of the main modules weights
 942            if GPA.pc.get_candidate_weight_init_by_main():
 943                main_module_abs = 0
 944                total_main_params = 0
 945                for main_param in neuron_main_module.parameters():
 946                    main_module_abs += main_param.abs().sum().item()
 947                    total_main_params += main_param.numel()
 948                if total_main_params > 0:
 949                    main_module_abs /= total_main_params
 950                else:
 951                    main_module_abs = 1.0
 952                multiplier = main_module_abs
 953            else:
 954                multiplier = 1.0
 955            param.data = (
 956                torch.randn(param.size(), dtype=param.dtype)
 957                * GPA.pc.get_candidate_weight_initialization_multiplier()
 958                * multiplier
 959            )
 960
 961
 962class PAIDendriteModule(nn.Module):
 963    """Module containing all dendrites modules added to the neuron module."""
 964
 965    def __init__(
 966        self,
 967        initial_module,
 968        activation_function_value=0.3,
 969        name="no_name_given",
 970        output_dimensions=None,
 971    ):
 972        """Initialize PAINeuronModule.
 973
 974        This function sets up the dendrite module to create candidate and permanent
 975        dendrite modules based on the initial_module provided.
 976
 977        Parameters
 978        ----------
 979        initial_module : nn.Module
 980            The module to copy.
 981        activation_function_value : float, optional
 982            A value associated with the activation function, by default 0.3.
 983        name : str
 984            The name of the neuron module.
 985        output_dimensions : vector, optional
 986            The dimensions of the input vector
 987        """
 988        super(PAIDendriteModule, self).__init__()
 989
 990        if output_dimensions is None:
 991            output_dimensions = []
 992
 993        self.layers = nn.ModuleList([])
 994        self.processors = []
 995        self.candidate_processors = []
 996        self.num_dendrites = 0
 997        # Number of dendrite cycles performed
 998        self.register_buffer(
 999            "num_cycles",
1000            torch.zeros(1, device=GPA.pc.get_device(), dtype=GPA.pc.get_d_type()),
1001        )
1002        self.mode = "n"
1003        self.name = name
1004        # Create a copy of the parent module so you don't have a pointer to the real one which causes save errors
1005        self.parent_module = UPA.deep_copy_pai(initial_module)
1006        if GPA.pc.get_perforated_backpropagation():
1007            MPB.set_ignored_parameters(self.parent_module)
1008        # Setup the input dimensions and node index for combining dendrite outputs
1009        if GPA.pc.get_perforated_backpropagation():
1010            MPB.create_extra_tensors(self)
1011        if output_dimensions == []:
1012            self.register_buffer(
1013                "this_output_dimensions", torch.tensor(GPA.pc.get_output_dimensions())
1014            )
1015        else:
1016            self.register_buffer(
1017                "this_output_dimensions", output_dimensions.detach().clone()
1018            )
1019        if (self.this_output_dimensions == 0).sum() != 1:
1020            print(f"1 need exactly one 0 in the input dimensions: {self.name}")
1021            print(self.this_output_dimensions)
1022            sys.exit(-1)
1023        self.register_buffer(
1024            "this_node_index", torch.tensor(GPA.pc.get_output_dimensions().index(0))
1025        )
1026
1027        # Initialize dendrite to dendrite connections
1028        self.dendrites_to_candidates = nn.ParameterList()
1029        self.dendrites_to_dendrites = nn.ParameterList()
1030
1031        # Store an activation function value if required
1032        self.activation_function_value = activation_function_value
1033        self.dendrite_values = nn.ModuleList([])
1034        for j in range(0, GPA.pc.get_global_candidates()):
1035            if GPA.pc.get_verbose():
1036                print(f"creating dendrite Values for {self.name}")
1037            self.dendrite_values.append(
1038                DendriteValueTracker(
1039                    False,
1040                    self.activation_function_value,
1041                    self.name,
1042                    self.this_output_dimensions,
1043                )
1044            )
1045        if GPA.pc.get_perforated_backpropagation():
1046            self.apply_pb_grads = MPB.apply_pb_grads.__get__(self, type(self))
1047            self.apply_pb_zero = MPB.apply_pb_zero.__get__(self, type(self))
1048
1049    def set_this_output_dimensions(self, new_output_dimensions):
1050        """Set input dimensions for dendrite module.
1051
1052        Signals to this DendriteModule that its input dimensions are different
1053        than the global default.
1054
1055        Parameters
1056        ----------
1057        new_output_dimensions : list
1058            A list or tensor specifying the new input dimensions.
1059        Returns
1060        -------
1061        None
1062
1063        """
1064
1065        if type(new_output_dimensions) is list:
1066            new_output_dimensions = torch.tensor(new_output_dimensions)
1067        delattr(self, "this_output_dimensions")
1068        self.register_buffer(
1069            "this_output_dimensions", new_output_dimensions.detach().clone()
1070        )
1071        if (new_output_dimensions == 0).sum() != 1:
1072            print(f"2 Need exactly one 0 in the input dimensions: {self.name}")
1073            print(new_output_dimensions)
1074            sys.exit(-1)
1075        self.this_node_index.copy_(
1076            (new_output_dimensions == 0).nonzero(as_tuple=True)[0][0]
1077        )
1078        for j in range(0, GPA.pc.get_global_candidates()):
1079            self.dendrite_values[j].set_this_output_dimensions(new_output_dimensions)
1080
1081    def create_new_dendrite_module(self, neuron_main_module):
1082        """Add a new set of dendrites."""
1083        # Candidate module
1084        self.candidate_module = nn.ModuleList([])
1085        # Copy that is unused for open source version
1086        self.best_candidate_module = nn.ModuleList([])
1087        if GPA.pc.get_verbose():
1088            print(self.name)
1089            print("Setting candidate processors")
1090        self.candidate_processors = []
1091        with torch.no_grad():
1092            for i in range(0, GPA.pc.get_global_candidates()):
1093
1094                new_module = UPA.deep_copy_pai(self.parent_module)
1095                init_params(new_module, neuron_main_module)
1096                self.candidate_module.append(new_module)
1097                self.best_candidate_module.append(UPA.deep_copy_pai(new_module))
1098                if type(self.parent_module) in GPA.pc.get_modules_with_processing():
1099                    module_index = GPA.pc.get_modules_with_processing().index(
1100                        type(self.parent_module)
1101                    )
1102                    self.candidate_processors.append(
1103                        GPA.pc.get_modules_processing_classes()[module_index]()
1104                    )
1105                elif (
1106                    type(self.parent_module).__name__
1107                    in GPA.pc.get_module_names_with_processing()
1108                ):
1109                    module_index = GPA.pc.get_module_names_with_processing().index(
1110                        type(self.parent_module).__name__
1111                    )
1112                    self.candidate_processors.append(
1113                        GPA.pc.get_module_by_name_processing_classes()[module_index]()
1114                    )
1115                if GPA.pc.get_perforated_backpropagation():
1116                    MPB.set_candidate_parameters(self.candidate_module[i])
1117                    MPB.set_ignored_parameters(self.best_candidate_module[i])
1118
1119        for i in range(0, GPA.pc.get_global_candidates()):
1120            self.candidate_module[i].to(GPA.pc.get_device())
1121            self.best_candidate_module[i].to(GPA.pc.get_device())
1122
1123        # Reset the dendrite_values objects
1124        for j in range(0, GPA.pc.get_global_candidates()):
1125            self.dendrite_values[j].reinitialize_for_pai()
1126
1127        # If there are already dendrites initialize the dendrite to dendrite connections
1128        if self.num_dendrites > 0:
1129            self.dendrites_to_candidates = nn.ParameterList()
1130            for j in range(0, GPA.pc.get_global_candidates()):
1131                self.dendrites_to_candidates.append(
1132                    nn.Parameter(
1133                        torch.zeros(
1134                            (self.num_dendrites, self.out_channels),
1135                            device=GPA.pc.get_device(),
1136                            dtype=GPA.pc.get_d_type(),
1137                        ),
1138                        requires_grad=True,
1139                    )
1140                )
1141                if GPA.pc.get_perforated_backpropagation():
1142                    MPB.init_candidates(self, j)
1143            if GPA.pc.get_perforated_backpropagation():
1144                MPB.set_candidate_parameters(self.dendrites_to_candidates)
1145            # Initialize best_dendrites_to_candidates_saved to snapshot peak-correlation weights at epoch boundaries
1146            self.best_dendrites_to_candidates_saved = []
1147            for j in range(0, GPA.pc.get_global_candidates()):
1148                self.best_dendrites_to_candidates_saved.append(
1149                    torch.zeros(
1150                        (self.num_dendrites, self.out_channels),
1151                        device=GPA.pc.get_device(),
1152                        dtype=GPA.pc.get_d_type(),
1153                    )
1154                )
1155
1156    def clear_processors(self):
1157        """Clear processors."""
1158        for processor in self.processors:
1159            if not processor:
1160                continue
1161            else:
1162                processor.clear_processor()
1163        for processor in self.candidate_processors:
1164            if not processor:
1165                continue
1166            else:
1167                processor.clear_processor()
1168
1169    def set_mode(self, mode):
1170        """Perform actions when switching between neuron and dendrite training.
1171
1172        Parameters
1173        ----------
1174        mode : str
1175            The mode to set. Either "n" for neuron training or "p" for pai-dendrite training.
1176
1177        Returns
1178        -------
1179        None
1180        """
1181
1182        self.mode = mode
1183        self.num_cycles += 1
1184        if GPA.pc.get_verbose():
1185            print(f"PAI calling set mode {mode} : {self.num_cycles}")
1186        print(f"Module {self.name} calling set mode {mode} : {self.num_cycles}")
1187        # When switching back to neuron training mode convert candidates modules into accepted modules
1188        if mode == "n":
1189            if GPA.pc.get_verbose():
1190                print("So calling all the things to add to modules")
1191            # Copy weights/bias from correct candidates
1192            if self.num_dendrites == 1:
1193                self.dendrites_to_dendrites = nn.ParameterList()
1194                self.dendrites_to_dendrites.append(torch.tensor([]))
1195            if self.num_dendrites >= 1:
1196                self.dendrites_to_dendrites.append(
1197                    torch.nn.Parameter(
1198                        torch.zeros(
1199                            [self.num_dendrites, self.out_channels],
1200                            device=GPA.pc.get_device(),
1201                            dtype=GPA.pc.get_d_type(),
1202                        ),
1203                        # Grad is true if not pb or if pb and dendrite_update_mode is true
1204                        requires_grad=(not GPA.pc.get_perforated_backpropagation())
1205                        or GPA.pc.get_dendrite_update_mode(),
1206                    )
1207                )
1208            with torch.no_grad():
1209                if GPA.pc.get_global_candidates() > 1:
1210                    print(
1211                        "This was a flag that will be needed if using multiple candidates. "
1212                        "It's not set up yet but nice work finding it."
1213                    )
1214                    print(
1215                        "Note: with multiple candidates, best-score ranking in new_best() uses "
1216                        "unnormalized covariance (prev_dendrite_candidate_correlation) rather than "
1217                        "the normalized correlation coefficient. Candidates with larger output "
1218                        "magnitude will be favored regardless of true correlation quality. "
1219                        "Fix by tracking running sigma_V and sigma_E and dividing in new_best()."
1220                    )
1221                    pdb.set_trace()
1222                plane_max_index = 0
1223                self.layers.append(
1224                    UPA.deep_copy_pai(self.best_candidate_module[plane_max_index])
1225                )
1226                self.layers[self.num_dendrites].to(GPA.pc.get_device())
1227                if self.num_dendrites > 0:
1228                    self.dendrites_to_dendrites[self.num_dendrites].copy_(
1229                        self.best_dendrites_to_candidates_saved[plane_max_index]
1230                    )
1231                if type(self.parent_module) in GPA.pc.get_modules_with_processing():
1232                    self.processors.append(self.candidate_processors[plane_max_index])
1233                if (
1234                    type(self.parent_module).__name__
1235                    in GPA.pc.get_module_names_with_processing()
1236                ):
1237                    self.processors.append(self.candidate_processors[plane_max_index])
1238            if GPA.pc.get_perforated_backpropagation():
1239                MPB.set_pb_mode(self, mode)
1240            del self.candidate_module, self.best_candidate_module
1241
1242            self.num_dendrites += 1
1243            if GPA.pc.get_perforated_backpropagation():
1244                MPB.set_dendrite_parameters(self.dendrites_to_dendrites)
1245                MPB.set_dendrite_parameters(self.layers)
1246
1247    def forward(self, *args, **kwargs):
1248        """Forward pass for dendrite module.
1249
1250        Parameters
1251        ----------
1252        *args : tuple
1253            Positional arguments for the forward pass.
1254        **kwargs : dict
1255            Keyword arguments for the forward pass.
1256
1257        Returns
1258        -------
1259        Any
1260            The output of the module after processing through the neuron and dendrite modules.
1261        Any
1262            Remaining outputs are only used for Perforated Backpropagation.
1263        Any
1264            Remaining outputs are only used for Perforated Backpropagation.
1265        Any
1266            Remaining outputs are only used for Perforated Backpropagation.
1267
1268        Notes
1269        -----
1270        If using Perforated Backpropagation, the additional outputs will be moved around in
1271        this code but left unused and only passed into separate PB functions.
1272        """
1273
1274        outs = {}
1275
1276        # For all modules apply processors, call the modules, then apply post processors
1277        args2, kwargs2 = args, kwargs
1278        for c in range(0, self.num_dendrites):
1279            if GPA.pc.get_perforated_backpropagation():
1280                args2, kwargs2 = MPB.preprocess_pb(*args, **kwargs)
1281            if self.processors != []:
1282                try:
1283                    args2, kwargs2 = self.processors[c].pre_d(*args2, **kwargs2)
1284                except Exception as e:
1285                    traceback.print_exc(limit=None, chain=True)
1286                    print(f"Your pre_d processor for {self.name} caused this error")
1287                    print(
1288                        f"You must check how this is defined and ensure that it is properly"
1289                    )
1290                    print(
1291                        f"accepting inputs to the PAIModule and returning what will then be"
1292                    )
1293                    print(f"the input to the dendrite module")
1294                    sys.exit()
1295            out_values = self.layers[c](*args2, **kwargs2)
1296            if self.processors != []:
1297                try:
1298                    outs[c] = self.processors[c].post_d(out_values)
1299                except Exception as e:
1300                    traceback.print_exc(limit=None, chain=True)
1301                    print(f"Your post_d processor for {self.name} caused this error")
1302                    print(
1303                        f"You must check how this is defined and ensure that it is properly"
1304                    )
1305                    print(
1306                        f"accepting outputs from the dendrite module and returning the"
1307                    )
1308                    print(
1309                        f"single tensor to be combined with the neurons output tensor"
1310                    )
1311                    sys.exit()
1312            else:
1313                outs[c] = out_values
1314
1315        # Create dendrite outputs
1316        # Each dendrite has input from previously created dendrites
1317        # So activation is added before the nonlinearity is called
1318        view_tuple = []
1319        for out_index in range(0, self.num_dendrites):
1320            current_out = outs[out_index]
1321            view_tuple = []
1322            for dim in range(len(current_out.shape)):
1323                if dim == self.this_node_index:
1324                    view_tuple.append(-1)
1325                    continue
1326                view_tuple.append(1)
1327
1328            for in_index in range(0, out_index):
1329                if view_tuple == [
1330                    1
1331                ]:  # This is only the case when passing a single datapoint rather than a batch
1332                    current_out = (
1333                        current_out
1334                        + self.dendrites_to_dendrites[out_index][in_index, :].to(
1335                            current_out.device
1336                        )
1337                        * outs[in_index]
1338                    )
1339                else:
1340                    current_out = (
1341                        current_out
1342                        + self.dendrites_to_dendrites[out_index][in_index, :]
1343                        .view(view_tuple)
1344                        .to(current_out.device)
1345                        * outs[in_index]
1346                    )
1347            outs[out_index] = GPA.pc.get_pai_forward_function()(current_out)
1348        # Return a dict which has all dendritic outputs after the activation functions were called
1349        if GPA.pc.get_perforated_backpropagation():
1350            candidate_outs, candidate_nonlinear_outs, candidate_non_zeroed = (
1351                MPB.forward_candidates(self, view_tuple, outs, *args2, **kwargs2)
1352            )
1353        else:
1354            candidate_outs, candidate_nonlinear_outs, candidate_non_zeroed = (
1355                {},
1356                {},
1357                {},
1358            )
1359        return outs, candidate_outs, candidate_nonlinear_outs, candidate_non_zeroed
1360
1361
1362class DendriteValueTracker(nn.Module):
1363    """Tracker object that maintains certain values for each set of dendrites."""
1364
1365    def __init__(
1366        self,
1367        initialized,
1368        activation_function_value,
1369        name,
1370        output_dimensions,
1371        out_channels=-1,
1372    ):
1373        """Initialize DendriteValueTracker.
1374
1375        This function sets up the value tracker to maintain statistics and values
1376        for each set of dendrites.
1377
1378        Parameters
1379        ----------
1380        initialized : int
1381            Whether the dendrite has been initialized (1) or not (0).
1382        activation_function_value : float
1383            A value associated with the activation function.
1384        name : str
1385            The name of the associated neuron module.
1386        output_dimensions : vector
1387            The dimensions of the input vector.
1388        out_channels : int
1389            The number of output channels
1390        """
1391        super(DendriteValueTracker, self).__init__()
1392
1393        self.layer_name = name
1394        for val_name in DENDRITE_INIT_VALUES:
1395            self.register_buffer(
1396                val_name,
1397                torch.zeros(1, device=GPA.pc.get_device(), dtype=GPA.pc.get_d_type()),
1398            )
1399        self.initialized[0] = initialized
1400        self.activation_function_value = activation_function_value
1401        self.register_buffer(
1402            "this_output_dimensions", output_dimensions.clone().detach()
1403        )
1404        if (self.this_output_dimensions == 0).sum() != 1:
1405            print(f"3 need exactly one 0 in the input dimensions: {self.layer_name}")
1406            print(self.this_output_dimensions)
1407            sys.exit(-1)
1408        self.register_buffer(
1409            "this_node_index", (output_dimensions == 0).nonzero(as_tuple=True)[0]
1410        )
1411        if out_channels != -1:
1412            self.setup_arrays(out_channels)
1413        else:
1414            self.out_channels = -1
1415
1416    def print(self):
1417        """Print value tracker information."""
1418        total_string = "Value Tracker:"
1419        for val_name in DENDRITE_INIT_VALUES:
1420            total_string += f"\t{val_name}:\n\t\t"
1421            total_string += getattr(self, val_name).__repr__()
1422            total_string += "\n"
1423        for val_name in get_DENDRITE_TENSOR_VALUES():
1424            if getattr(self, val_name, None) is not None:
1425                total_string += f"\t{val_name}:\n\t\t"
1426                total_string += getattr(self, val_name).__repr__()
1427                total_string += "\n"
1428        print(total_string)
1429
1430    def set_this_output_dimensions(self, new_output_dimensions):
1431        """Set input dimensions for value tracker
1432
1433        Signals to this DendriteValueTracker that its input dimensions are different
1434        than the global default.
1435
1436        Parameters
1437        ----------
1438        new_output_dimensions : list
1439            A list or tensor specifying the new input dimensions.
1440        Returns
1441        -------
1442        None
1443
1444        """
1445        if type(new_output_dimensions) is list:
1446            new_output_dimensions = torch.tensor(new_output_dimensions)
1447        delattr(self, "this_output_dimensions")
1448        self.register_buffer(
1449            "this_output_dimensions", new_output_dimensions.detach().clone()
1450        )
1451        if (new_output_dimensions == 0).sum() != 1:
1452            print(f"4 need exactly one 0 in the input dimensions: {self.layer_name}")
1453            print(new_output_dimensions)
1454            sys.exit(-1)
1455        self.this_node_index.copy_(
1456            (new_output_dimensions == 0).nonzero(as_tuple=True)[0][0]
1457        )
1458
1459    def set_out_channels(self, shape_values):
1460        """Set output channels based on shape values and saved node index
1461
1462        Parameters
1463        ----------
1464        shape_values : list or torch.Size
1465            A list or tensor specifying the shape values.
1466
1467        Returns
1468        -------
1469        None
1470        """
1471        if type(shape_values) == torch.Size:
1472            self.out_channels = int(shape_values[self.this_node_index])
1473        else:
1474            self.out_channels = int(shape_values[self.this_node_index].item())
1475
1476    def setup_arrays(self, out_channels):
1477        """Setup arrays for value tracker.
1478
1479        Parameters
1480        ----------
1481        out_channels : int
1482            The number of output channels.
1483        Returns
1484        -------
1485        None
1486
1487        """
1488        self.out_channels = out_channels
1489        for val_name in get_DENDRITE_TENSOR_VALUES():
1490            self.register_buffer(
1491                val_name,
1492                torch.zeros(
1493                    out_channels, device=GPA.pc.get_device(), dtype=GPA.pc.get_d_type()
1494                ),
1495            )
1496
1497        for name in get_VALUE_TRACKER_ARRAYS():
1498            setattr(self, name, {})
1499            count = 1
1500            if torch.cuda.device_count() > count:
1501                count = torch.cuda.device_count()
1502            for i in range(count):
1503                getattr(self, name)[i] = []
1504        for val_name in get_DENDRITE_SINGLE_VALUES():
1505            self.register_buffer(
1506                val_name,
1507                torch.zeros(1, device=GPA.pc.get_device(), dtype=GPA.pc.get_d_type()),
1508            )
1509
1510    def reinitialize_for_pai(self):
1511        """Reinitialize value tracker to add the next set of dendrites"""
1512
1513        if self.out_channels == -1:
1514            print("You have a perforated module that was never initialized")
1515            print("This likely means it is not being added to the autograd graph")
1516            print("Check your forward function that it is actually being used")
1517            print("If its not you should really delete it, but you can also add")
1518            print(self.layer_name)
1519            print("with:")
1520            print("GPA.pc.append_module_ids_to_track(['" + self.layer_name + "'])")
1521            print("This can also happen while testing_dendrite_capacity if you")
1522            print(
1523                "run a validation cycle and try to add Dendrites before doing any training.\n"
1524            )
1525            pdb.set_trace()
1526
1527        self.initialized[0] = 0
1528        if GPA.pc.get_perforated_backpropagation():
1529            MPB.reinitialize_for_pb(self)
1530        else:
1531            for val_name in get_DENDRITE_REINIT_VALUES():
1532                setattr(self, val_name, getattr(self, val_name) * 0)
DENDRITE_INIT_VALUES = ['initialized', 'current_d_init']
def get_DENDRITE_TENSOR_VALUES():
50def get_DENDRITE_TENSOR_VALUES():
51    """Get DENDRITE_TENSOR_VALUES, updating from MPB if perforated_backpropagation is enabled."""
52    global _cached_dendrite_tensor_values, _cached_dendrite_tensor_pb_state
53    current_pb_state = GPA.pc.get_perforated_backpropagation()
54
55    if (
56        _cached_dendrite_tensor_values is None
57        or _cached_dendrite_tensor_pb_state != current_pb_state
58    ):
59        _cached_dendrite_tensor_pb_state = current_pb_state
60        if current_pb_state:
61            _cached_dendrite_tensor_values = MPB.update_dendrite_tensor_values(
62                _DENDRITE_TENSOR_VALUES_BASE.copy()
63            )
64        else:
65            _cached_dendrite_tensor_values = _DENDRITE_TENSOR_VALUES_BASE.copy()
66
67    return _cached_dendrite_tensor_values

Get DENDRITE_TENSOR_VALUES, updating from MPB if perforated_backpropagation is enabled.

def get_DENDRITE_SINGLE_VALUES():
70def get_DENDRITE_SINGLE_VALUES():
71    """Get DENDRITE_SINGLE_VALUES, updating from MPB if perforated_backpropagation is enabled."""
72    global _cached_dendrite_single_values, _cached_dendrite_single_pb_state
73    current_pb_state = GPA.pc.get_perforated_backpropagation()
74
75    if (
76        _cached_dendrite_single_values is None
77        or _cached_dendrite_single_pb_state != current_pb_state
78    ):
79        _cached_dendrite_single_pb_state = current_pb_state
80        if current_pb_state:
81            _cached_dendrite_single_values = MPB.update_dendrite_single_values(
82                _DENDRITE_SINGLE_VALUES_BASE.copy()
83            )
84        else:
85            _cached_dendrite_single_values = _DENDRITE_SINGLE_VALUES_BASE.copy()
86
87    return _cached_dendrite_single_values

Get DENDRITE_SINGLE_VALUES, updating from MPB if perforated_backpropagation is enabled.

def get_VALUE_TRACKER_ARRAYS():
 90def get_VALUE_TRACKER_ARRAYS():
 91    """Get VALUE_TRACKER_ARRAYS, updating from MPB if perforated_backpropagation is enabled."""
 92    global _cached_value_tracker_arrays, _cached_value_tracker_pb_state
 93    current_pb_state = GPA.pc.get_perforated_backpropagation()
 94
 95    if (
 96        _cached_value_tracker_arrays is None
 97        or _cached_value_tracker_pb_state != current_pb_state
 98    ):
 99        _cached_value_tracker_pb_state = current_pb_state
100        if current_pb_state:
101            _cached_value_tracker_arrays = MPB.update_value_tracker_arrays(
102                _VALUE_TRACKER_ARRAYS_BASE.copy()
103            )
104        else:
105            _cached_value_tracker_arrays = _VALUE_TRACKER_ARRAYS_BASE.copy()
106
107    return _cached_value_tracker_arrays

Get VALUE_TRACKER_ARRAYS, updating from MPB if perforated_backpropagation is enabled.

def get_DENDRITE_REINIT_VALUES():
110def get_DENDRITE_REINIT_VALUES():
111    """Get DENDRITE_REINIT_VALUES."""
112    return get_DENDRITE_TENSOR_VALUES() + get_DENDRITE_SINGLE_VALUES()

Get DENDRITE_REINIT_VALUES.

def get_DENDRITE_SAVE_VALUES():
115def get_DENDRITE_SAVE_VALUES():
116    """Get DENDRITE_SAVE_VALUES."""
117    return (
118        get_DENDRITE_TENSOR_VALUES()
119        + get_DENDRITE_SINGLE_VALUES()
120        + DENDRITE_INIT_VALUES
121    )

Get DENDRITE_SAVE_VALUES.

def filter_backward(grad_out, values):
124def filter_backward(grad_out, values):
125    """Filter backward pass for gradient processing.
126
127    This function processes gradients during the backward pass,
128    ensuring correct input dimensions,and applying perforated backpropagation if enabled.
129
130    Parameters
131    ----------
132    grad_out : torch.Tensor
133        The gradient output tensor from the backward pass.
134    values : DendriteValueTracker
135        A DendriteValueTracker instance containing values associated with the module being processed.
136
137    Returns
138    -------
139    None
140    """
141    if GPA.pc.get_extra_verbose():
142        print(f"{values[0].layer_name} calling backward")
143
144    with torch.no_grad():
145        val = grad_out.detach()
146        # If the input dimensions are not initialized
147        if not values[0].current_d_init.item():
148            # If input dimensions and gradient don't have same shape trigger error and quit
149            if len(values[0].this_output_dimensions) != len(grad_out.shape):
150                print(
151                    "The following module has not properly set this_output_dimensions"
152                )
153                print(values[0].layer_name)
154                print("it is expecting:")
155                print(values[0].this_output_dimensions)
156                print("but received")
157                print(grad_out.shape)
158                print(
159                    "to check these all at once set GPA.pc.set_debugging_output_dimensions(1)"
160                )
161                print(
162                    f"Call MODEL_VARIABLE{values[0].layer_name}.set_this_output_dimensions([...]) on this module after perforate_model"
163                )
164                print(
165                    "where the ... is replaced with the correct vector as described in section 4 of customization.md"
166                )
167                if not GPA.pc.get_debugging_output_dimensions():
168                    sys.exit(0)
169                else:
170                    GPA.pc.set_debugging_output_dimensions(2)
171                    return
172            # Make sure that the input dimensions are correct
173            for i in range(len(values[0].this_output_dimensions)):
174                if values[0].this_output_dimensions[i] == 0:
175                    continue
176                # Make sure all input dimensions are either -1 (new format) or exact values (old format)
177                if (
178                    not (grad_out.shape[i] == values[0].this_output_dimensions[i])
179                    and not values[0].this_output_dimensions[i] == -1
180                ):
181                    print(
182                        "The following module has not properly set this_output_dimensions with this incorrect shape"
183                    )
184                    print(values[0].layer_name)
185                    print("it is expecting:")
186                    print(values[0].this_output_dimensions)
187                    print("but received")
188                    print(grad_out.shape)
189                    print(
190                        "to check these all at once set GPA.pc.set_debugging_output_dimensions(1)"
191                    )
192                    if not GPA.pc.get_debugging_output_dimensions():
193                        sys.exit(0)
194                    else:
195                        GPA.pc.set_debugging_output_dimensions(2)
196                        return
197            # Setup the arrays with the now known shape
198            with torch.no_grad():
199                if GPA.pc.get_verbose():
200                    print("setting d shape for")
201                    print(values[0].layer_name)
202                    print(val.size())
203
204                values[0].set_out_channels(val.size())
205                values[0].setup_arrays(values[0].out_channels)
206            # Flag that it has been setup
207            values[0].current_d_init[0] = 1
208        if GPA.pc.get_perforated_backpropagation():
209            MPB.filter_backward_pb(val, values)

Filter backward pass for gradient processing.

This function processes gradients during the backward pass, ensuring correct input dimensions,and applying perforated backpropagation if enabled.

Parameters
  • grad_out (torch.Tensor): The gradient output tensor from the backward pass.
  • values (DendriteValueTracker): A DendriteValueTracker instance containing values associated with the module being processed.
Returns
  • None
def set_wrapped_params(model):
212def set_wrapped_params(model):
213    """Set parameters as wrapped with dendrites.
214
215    Parameters
216    ----------
217    model : torch.nn.Module
218        The model whose parameters are to be marked as wrapped.
219
220    Returns
221    -------
222    None
223
224    """
225    for param in model.parameters():
226        param.wrapped = True

Set parameters as wrapped with dendrites.

Parameters
  • model (torch.nn.Module): The model whose parameters are to be marked as wrapped.
Returns
  • None
def set_tracked_params(model):
229def set_tracked_params(model):
230    """Set parameters as tracked without dendrites.
231
232    Parameters
233    ----------
234    model : torch.nn.Module
235        The model whose parameters are to be marked as tracked.
236
237    Returns
238    -------
239    None
240    """
241    for param in model.parameters():
242        param.tracked = True

Set parameters as tracked without dendrites.

Parameters
  • model (torch.nn.Module): The model whose parameters are to be marked as tracked.
Returns
  • None
class PAINeuronModule(torch.nn.modules.module.Module):
245class PAINeuronModule(nn.Module):
246    """Wrapper to set a module as one that will have dendritic copies."""
247
248    def __init__(self, start_module, name):
249        """Initialize PAINeuronModule.
250
251        This function sets up the neuron module to wrap the start_module
252        and manage its dendritic connections.
253
254        Parameters
255        ----------
256        start_module : nn.Module
257            The module to wrap.
258        name : str
259            The name of the neuron module.
260        """
261        super(PAINeuronModule, self).__init__()
262
263        if isinstance(start_module, nn.Module):
264            self.main_module = start_module
265        else:
266            print("start_module must be nn.Module: %s" % name)
267            print(type(start_module))
268            print(start_module)
269            sys.exit(-1)
270        self.name = name
271        # Per-module config: loads custom settings from {save_name}_config.json if present.
272        # Passes both the instance name (id) and the module type so load_config can
273        # fall back to type-level settings when no name-specific entry exists.
274        _module_type_name = type(start_module).__name__
275        self.module_config = GPA.PAIConfig(
276            module_name=self.name, module_type=_module_type_name
277        )
278
279        set_wrapped_params(self.main_module)
280        if self.module_config.get_verbose():
281            print(
282                f"initing a module {self.name} with main type {type(self.main_module)}"
283            )
284            print(start_module)
285
286        # If this main_module is one that requires processing set the processor
287        if type(self.main_module) in self.module_config.get_modules_with_processing():
288            module_index = self.module_config.get_modules_with_processing().index(
289                type(self.main_module)
290            )
291            self.processor = self.module_config.get_modules_processing_classes()[
292                module_index
293            ]()
294            if self.module_config.get_verbose():
295                print("with processor")
296                print(self.processor)
297        elif (
298            type(self.main_module).__name__
299            in self.module_config.get_module_names_with_processing()
300        ):
301            module_index = self.module_config.get_module_names_with_processing().index(
302                type(self.main_module).__name__
303            )
304            self.processor = self.module_config.get_module_by_name_processing_classes()[
305                module_index
306            ]()
307            if self.module_config.get_verbose():
308                print("with processor")
309                print(self.processor)
310        else:
311            self.processor = None
312
313        # Field that can be filled in if your activation function requires a parameter
314        self.activation_function_value = -1
315        self.type = "neuron_module"
316
317        self.register_buffer(
318            "this_output_dimensions",
319            (torch.tensor(self.module_config.get_output_dimensions())),
320        )
321        if (self.this_output_dimensions == 0).sum() != 1:
322            print(f"5 Need exactly one 0 in the input dimensions: {self.name}")
323            print(self.this_output_dimensions)
324            sys.exit(-1)
325        self.register_buffer(
326            "this_node_index",
327            torch.tensor(self.module_config.get_output_dimensions().index(0)),
328        )
329        self.dendrite_modules_added = 0
330
331        # Values for dendrite to neuron weights
332        self.dendrites_to_top = nn.ParameterList()
333        self.register_parameter("newest_dendrite_to_top", None)
334        self.candidate_to_top = nn.ParameterList()
335        self.register_parameter("current_candidate_to_top", None)
336        # Create the dendrite module
337        self.dendrite_module = PAIDendriteModule(
338            self.main_module,
339            activation_function_value=self.activation_function_value,
340            name=self.name,
341            output_dimensions=self.this_output_dimensions,
342        )
343        print(self.this_output_dimensions[2:])
344        print(type(start_module))
345        # If it is linear and default has convolutional dimensions, automatically set to just be batch size and neuron indexes
346        if (
347            issubclass(type(start_module), nn.Linear)
348            or (
349                issubclass(type(start_module), GPA.PAISequential)
350                and issubclass(type(start_module.model[0]), nn.Linear)
351            )
352        ) and (
353            np.array(self.this_output_dimensions)[2:] == -1
354        ).all():  # Everything past 2 is a negative 1
355            self.set_this_output_dimensions(self.this_output_dimensions[0:2])
356        if (
357            issubclass(type(start_module), nn.Conv1d)
358            or (
359                issubclass(type(start_module), GPA.PAISequential)
360                and issubclass(type(start_module.model[0]), nn.Conv1d)
361            )
362        ) and (
363            np.array(self.this_output_dimensions)[3:] == -1
364        ).all():  # Everything past 2 is a negative 1
365            self.set_this_output_dimensions(self.this_output_dimensions[0:3])
366        # Apply per-module output_dimensions override from config if present
367        _custom_dims = self.module_config.__dict__.get("_output_dimensions")
368        if _custom_dims is not None:
369            self.set_this_output_dimensions(torch.tensor(_custom_dims))
370        GPA.pai_tracker.add_pai_neuron_module(self)
371        if self.module_config.get_perforated_backpropagation():
372            MPB.set_neuron_parameters(self.main_module)
373
374    def __getattr__(self, name):
375        """Get member variables from the main module.
376
377        Parameters
378        ----------
379        name : str
380            The name of the variable to retrieve.
381        Returns
382        -------
383        The requested variable.
384
385        Notes
386        -----
387        This method first attempts to retrieve the attribute from the PAINeuronModule instance.
388        If it fails, it tries to get the attribute from the wrapped main_module.
389        This allows seamless access to the main module's attributes without modifying original code.
390        """
391        try:
392            return super().__getattr__(name)
393        except AttributeError:
394            return getattr(self.main_module, name)
395
396    def __getitem__(self, index):
397        """Support indexing operations on the main module.
398
399        Parameters
400        ----------
401        index : int or slice
402            The index or slice to retrieve.
403
404        Returns
405        -------
406        The indexed item from the main module.
407        """
408        return self.main_module[index]
409
410    def apply_pb_grads(self):
411        """Apply perforated backpropagation gradients if enabled."""
412        self.dendrite_module.apply_pb_grads()
413
414    def apply_pb_zero(self):
415        """Clear leftover saved tensors if there are any."""
416        self.dendrite_module.apply_pb_zero()
417
418    def clear_processors(self):
419        """Clear processors if they save values for DeepCopy and save.
420
421        Parameters
422        ----------
423        None
424
425        Returns
426        -------
427        None
428        """
429
430        if not self.processor:
431            return
432        else:
433            self.processor.clear_processor()
434            self.dendrite_module.clear_processors()
435
436    def clear_dendrites(self):
437        """Clear and reset dendrites before loading from a state dict.
438
439        Parameters
440        ----------
441        None
442
443        Returns
444        -------
445        None
446
447        """
448        self.dendrite_modules_added = 0
449        self.dendrites_to_top = nn.ParameterList()
450        self.candidate_to_top = nn.ParameterList()
451        self.dendrite_module = PAIDendriteModule(
452            self.main_module,
453            activation_function_value=self.activation_function_value,
454            name=self.name,
455            output_dimensions=self.this_output_dimensions,
456        )
457
458    def __str__(self):
459        """String representation of the module.
460
461        Parameters
462        ----------
463        None
464
465        Returns
466        -------
467        str
468            String representation of the module.
469
470        Notes
471        -----
472        Setting for verbose changes level of details in the string output.
473        """
474        # If verbose print the whole module otherwise just print the module type as a PAIModule
475        if self.module_config.get_verbose():
476            total_string = self.main_module.__str__()
477            total_string = "PAIModule(" + total_string + ")"
478            return total_string + self.dendrite_module.__str__()
479        else:
480            total_string = self.main_module.__str__()
481            total_string = "PAIModule(" + total_string + ")"
482            return total_string
483
484    def __repr__(self):
485        """Representation of the module."""
486        return self.__str__()
487
488    def set_this_output_dimensions(self, new_output_dimensions):
489        """Set the input dimensions for the neuron and dendrite blocks.
490
491        Signals to this NeuronModule that its input dimensions are different
492        than the global default.
493
494        Parameters
495        ----------
496        new_output_dimensions : list
497            A list or tensor specifying the new input dimensions.
498        Returns
499        -------
500        None
501
502        """
503        if type(new_output_dimensions) is list:
504            new_output_dimensions = torch.tensor(new_output_dimensions)
505        delattr(self, "this_output_dimensions")
506        self.register_buffer(
507            "this_output_dimensions", new_output_dimensions.detach().clone()
508        )
509        if (new_output_dimensions == 0).sum() != 1:
510            print(f"6 need exactly one 0 in the input dimensions: {self.name}")
511            print(new_output_dimensions)
512        self.this_node_index.copy_(
513            (new_output_dimensions == 0).nonzero(as_tuple=True)[0][0]
514        )
515        self.dendrite_module.set_this_output_dimensions(new_output_dimensions)
516
517    def set_mode(self, mode):
518        """Switch between neuron training and dendrite training.
519
520        Parameters
521        ----------
522        mode : str
523            The mode to set. Either "n" for neuron training or "p" for pai-dendrite training.
524
525        Returns
526        -------
527        bool
528            True if mode was set successfully, False otherwise.
529
530        Notes
531        -----
532        If False is returned, the mode was not changed due to an error.
533        This is a problem that should not be ignored, but it can be ignored
534        by calling PGA.pc.set_checked_skipped_modules(True)
535        """
536
537        if self.module_config.get_verbose():
538            print(f"{self.name} calling set mode {mode}")
539        # If returning to neuron training
540        if mode == "n":
541            self.dendrite_module.set_mode(mode)
542            # Initialize the dendrite to neuron connections
543            if self.dendrite_modules_added > 0:
544                if self.module_config.get_learn_dendrites_live():
545                    values = torch.cat(
546                        (
547                            self.dendrites_to_top[self.dendrite_modules_added - 1],
548                            nn.Parameter(
549                                self.candidate_to_top.detach()
550                                .clone()
551                                .to(dtype=self.module_config.get_d_type())
552                            ),
553                        ),
554                        0,
555                    )
556                else:
557                    values = torch.cat(
558                        (
559                            self.dendrites_to_top[self.dendrite_modules_added - 1],
560                            nn.Parameter(
561                                torch.zeros(
562                                    (1, self.out_channels),
563                                    device=self.dendrites_to_top[
564                                        self.dendrite_modules_added - 1
565                                    ].device,
566                                    dtype=self.module_config.get_d_type(),
567                                )
568                            ),
569                        ),
570                        0,
571                    )
572                self.dendrites_to_top.append(
573                    nn.Parameter(
574                        values.detach()
575                        .clone()
576                        .to(
577                            device=self.module_config.get_device(),
578                            dtype=self.module_config.get_d_type(),
579                        ),
580                        requires_grad=True,
581                    )
582                )
583            else:
584                if self.module_config.get_learn_dendrites_live():
585                    self.dendrites_to_top.append(
586                        nn.Parameter(
587                            self.candidate_to_top.detach()
588                            .clone()
589                            .to(dtype=self.module_config.get_d_type()),
590                            requires_grad=True,
591                        )
592                    )
593                else:
594                    self.dendrites_to_top.append(
595                        nn.Parameter(
596                            torch.zeros(
597                                (1, self.out_channels),
598                                device=self.module_config.get_device(),
599                                dtype=self.module_config.get_d_type(),
600                            )
601                            .detach()
602                            .clone(),
603                            requires_grad=True,
604                        )
605                    )
606            self.dendrite_modules_added += 1
607            if self.module_config.get_perforated_backpropagation():
608                MPB.set_module_n_pb(self)
609                MPB.set_neuron_parameters(self.dendrites_to_top)
610
611        # If starting dendrite training
612        else:
613            try:
614                # Save the values that were calculated in filter_backward
615                self.out_channels = self.dendrite_module.dendrite_values[0].out_channels
616                self.dendrite_module.out_channels = (
617                    self.dendrite_module.dendrite_values[0].out_channels
618                )
619            except Exception as e:
620                print(e)
621                print(
622                    f"this occurred in module: {self.dendrite_module.dendrite_values[0].layer_name}"
623                )
624                print(
625                    "Module should be added to module_names_to_track so it doesn't have dendrites added"
626                )
627                print("If you are getting here but out_channels has not been set")
628                print(
629                    "A common reason is that this module never had gradients flow through it."
630                )
631                print("I have seen this happen because:")
632                print("-The weights were frozen (requires_grad = False)")
633                print(
634                    "-A model is added but not used so it was converted to a perforated module initialized"
635                )
636                print(
637                    "-A module was converted that doesn't have weights that get modified so backward doesn't flow through it"
638                )
639                print(
640                    "If this is normal behavior set GPA.pc.set_checked_skipped_modules(True) in the main to ignore"
641                )
642                print(
643                    "You can also set right now in this pdb terminal to have this not happen more after checking all modules this cycle."
644                )
645                if not self.module_config.get_checked_skipped_modules():
646                    pdb.set_trace()
647                return False
648            # Only change mode if it makes it past the above exception
649            self.dendrite_module.set_mode(mode)
650            if self.module_config.get_perforated_backpropagation():
651                MPB.set_module_p_pb(self)
652        return True
653
654    def create_new_dendrite_module(self):
655        """Add an additional dendrite module.
656
657        Parameters
658        ----------
659        None
660
661        Returns
662        -------
663        None
664        """
665        self.dendrite_module.create_new_dendrite_module(self.main_module)
666
667    def forward(self, *args, **kwargs):
668        """Forward pass through the neuron module.
669
670        Parameters
671        ----------
672        *args : tuple
673            Positional arguments for the forward pass.
674        **kwargs : dict
675            Keyword arguments for the forward pass.
676
677        Returns
678        -------
679        Any
680            The output of the module after processing through the neuron and dendrite modules.
681
682        Notes
683        -----
684            The output of this forward function will have the same format as the output
685            of the original module
686        """
687
688        # If debugging all input dimensions, quit program on first forward call
689        if self.module_config.get_debugging_output_dimensions() == 2:
690            print("all input dim problems now printed")
691            sys.exit(0)
692        if self.module_config.get_extra_verbose():
693            print(f"{self.name} calling forward")
694        # Call the main modules forward
695        out = self.main_module(*args, **kwargs)
696        # Filter with the processor if required
697        if self.processor is not None:
698            try:
699                out = self.processor.post_n1(out)
700            except Exception as e:
701                traceback.print_exc(limit=None, chain=True)
702                print(f"Your post_n1 processor for {self.name} caused this error")
703                print(
704                    f"You must check how this is defined and ensure that it is properly"
705                )
706                print(f"accepting outputs from the neuron module and returning the")
707                print(f"single tensor to be combined with the dendrites output tensor")
708                sys.exit()
709        # Call the forwards for all of the Dendrites
710        (
711            dendrite_outs,
712            candidate_outs,
713            candidate_nonlinear_outs,
714            candidate_outs_non_zeroed,
715        ) = self.dendrite_module(*args, **kwargs)
716        # If there are dendrites add all of their outputs to the neurons output
717        if self.dendrite_modules_added > 0:
718            for i in range(0, self.dendrite_modules_added):
719                to_top = self.dendrites_to_top[self.dendrite_modules_added - 1][i, :]
720                for dim in range(len(dendrite_outs[i].shape)):
721                    if dim == self.this_node_index:
722                        continue
723                    to_top = to_top.unsqueeze(dim)
724                if self.module_config.get_confirm_correct_sizes():
725                    to_top = to_top.expand(
726                        list(dendrite_outs[i].size())[0 : self.this_node_index]
727                        + [self.out_channels]
728                        + list(dendrite_outs[i].size())[self.this_node_index + 1 :]
729                    )
730                out = out + (dendrite_outs[i].to(out.device) * to_top.to(out.device))
731
732        # If learning live, add the candidate's output to the neuron's output via the live weight
733        if self.module_config.get_perforated_backpropagation():
734            out = MPB.apply_live_candidate_to_output(
735                self, out, candidate_nonlinear_outs
736            )
737
738        # Catch if processors are required
739        if type(out) is tuple:
740            print(self)
741            print(
742                f"The output of the above module {self.name} is a tuple when it must be a single tensor"
743            )
744            print(
745                "This must be fixed to enable the dendrite and neuron output to be combined"
746            )
747            print(
748                "Look in the API customization.md at section 2.2 regarding processors to fix this."
749            )
750            pdb.set_trace()
751
752        # Call filter backward to ensure the neuron index is setup correctly
753        if out.requires_grad:
754            out.register_hook(
755                lambda grad: filter_backward(grad, self.dendrite_module.dendrite_values)
756            )
757
758        # If there is a processor apply the second neuron stage
759        if self.processor is not None:
760            try:
761                out = self.processor.post_n2(out)
762            except Exception as e:
763                traceback.print_exc(limit=None, chain=True)
764                print(f"Your post_n2 processor for {self.name} caused this error")
765                print(
766                    f"You must check how this is defined and ensure that it is properly"
767                )
768                print(
769                    f"accepting the output tensor after combining the neuron's output "
770                )
771                print(f"with the dendrite's output and returning something that is the")
772                print(f"same format as your original module's return")
773                sys.exit()
774        return out

Wrapper to set a module as one that will have dendritic copies.

PAINeuronModule(start_module, name)
248    def __init__(self, start_module, name):
249        """Initialize PAINeuronModule.
250
251        This function sets up the neuron module to wrap the start_module
252        and manage its dendritic connections.
253
254        Parameters
255        ----------
256        start_module : nn.Module
257            The module to wrap.
258        name : str
259            The name of the neuron module.
260        """
261        super(PAINeuronModule, self).__init__()
262
263        if isinstance(start_module, nn.Module):
264            self.main_module = start_module
265        else:
266            print("start_module must be nn.Module: %s" % name)
267            print(type(start_module))
268            print(start_module)
269            sys.exit(-1)
270        self.name = name
271        # Per-module config: loads custom settings from {save_name}_config.json if present.
272        # Passes both the instance name (id) and the module type so load_config can
273        # fall back to type-level settings when no name-specific entry exists.
274        _module_type_name = type(start_module).__name__
275        self.module_config = GPA.PAIConfig(
276            module_name=self.name, module_type=_module_type_name
277        )
278
279        set_wrapped_params(self.main_module)
280        if self.module_config.get_verbose():
281            print(
282                f"initing a module {self.name} with main type {type(self.main_module)}"
283            )
284            print(start_module)
285
286        # If this main_module is one that requires processing set the processor
287        if type(self.main_module) in self.module_config.get_modules_with_processing():
288            module_index = self.module_config.get_modules_with_processing().index(
289                type(self.main_module)
290            )
291            self.processor = self.module_config.get_modules_processing_classes()[
292                module_index
293            ]()
294            if self.module_config.get_verbose():
295                print("with processor")
296                print(self.processor)
297        elif (
298            type(self.main_module).__name__
299            in self.module_config.get_module_names_with_processing()
300        ):
301            module_index = self.module_config.get_module_names_with_processing().index(
302                type(self.main_module).__name__
303            )
304            self.processor = self.module_config.get_module_by_name_processing_classes()[
305                module_index
306            ]()
307            if self.module_config.get_verbose():
308                print("with processor")
309                print(self.processor)
310        else:
311            self.processor = None
312
313        # Field that can be filled in if your activation function requires a parameter
314        self.activation_function_value = -1
315        self.type = "neuron_module"
316
317        self.register_buffer(
318            "this_output_dimensions",
319            (torch.tensor(self.module_config.get_output_dimensions())),
320        )
321        if (self.this_output_dimensions == 0).sum() != 1:
322            print(f"5 Need exactly one 0 in the input dimensions: {self.name}")
323            print(self.this_output_dimensions)
324            sys.exit(-1)
325        self.register_buffer(
326            "this_node_index",
327            torch.tensor(self.module_config.get_output_dimensions().index(0)),
328        )
329        self.dendrite_modules_added = 0
330
331        # Values for dendrite to neuron weights
332        self.dendrites_to_top = nn.ParameterList()
333        self.register_parameter("newest_dendrite_to_top", None)
334        self.candidate_to_top = nn.ParameterList()
335        self.register_parameter("current_candidate_to_top", None)
336        # Create the dendrite module
337        self.dendrite_module = PAIDendriteModule(
338            self.main_module,
339            activation_function_value=self.activation_function_value,
340            name=self.name,
341            output_dimensions=self.this_output_dimensions,
342        )
343        print(self.this_output_dimensions[2:])
344        print(type(start_module))
345        # If it is linear and default has convolutional dimensions, automatically set to just be batch size and neuron indexes
346        if (
347            issubclass(type(start_module), nn.Linear)
348            or (
349                issubclass(type(start_module), GPA.PAISequential)
350                and issubclass(type(start_module.model[0]), nn.Linear)
351            )
352        ) and (
353            np.array(self.this_output_dimensions)[2:] == -1
354        ).all():  # Everything past 2 is a negative 1
355            self.set_this_output_dimensions(self.this_output_dimensions[0:2])
356        if (
357            issubclass(type(start_module), nn.Conv1d)
358            or (
359                issubclass(type(start_module), GPA.PAISequential)
360                and issubclass(type(start_module.model[0]), nn.Conv1d)
361            )
362        ) and (
363            np.array(self.this_output_dimensions)[3:] == -1
364        ).all():  # Everything past 2 is a negative 1
365            self.set_this_output_dimensions(self.this_output_dimensions[0:3])
366        # Apply per-module output_dimensions override from config if present
367        _custom_dims = self.module_config.__dict__.get("_output_dimensions")
368        if _custom_dims is not None:
369            self.set_this_output_dimensions(torch.tensor(_custom_dims))
370        GPA.pai_tracker.add_pai_neuron_module(self)
371        if self.module_config.get_perforated_backpropagation():
372            MPB.set_neuron_parameters(self.main_module)

Initialize PAINeuronModule.

This function sets up the neuron module to wrap the start_module and manage its dendritic connections.

Parameters
  • start_module (nn.Module): The module to wrap.
  • name (str): The name of the neuron module.
name
module_config
activation_function_value
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

dendrite_modules_added
dendrites_to_top
candidate_to_top
dendrite_module
def apply_pb_grads(self):
410    def apply_pb_grads(self):
411        """Apply perforated backpropagation gradients if enabled."""
412        self.dendrite_module.apply_pb_grads()

Apply perforated backpropagation gradients if enabled.

def apply_pb_zero(self):
414    def apply_pb_zero(self):
415        """Clear leftover saved tensors if there are any."""
416        self.dendrite_module.apply_pb_zero()

Clear leftover saved tensors if there are any.

def clear_processors(self):
418    def clear_processors(self):
419        """Clear processors if they save values for DeepCopy and save.
420
421        Parameters
422        ----------
423        None
424
425        Returns
426        -------
427        None
428        """
429
430        if not self.processor:
431            return
432        else:
433            self.processor.clear_processor()
434            self.dendrite_module.clear_processors()

Clear processors if they save values for DeepCopy and save.

Parameters
  • None
Returns
  • None
def clear_dendrites(self):
436    def clear_dendrites(self):
437        """Clear and reset dendrites before loading from a state dict.
438
439        Parameters
440        ----------
441        None
442
443        Returns
444        -------
445        None
446
447        """
448        self.dendrite_modules_added = 0
449        self.dendrites_to_top = nn.ParameterList()
450        self.candidate_to_top = nn.ParameterList()
451        self.dendrite_module = PAIDendriteModule(
452            self.main_module,
453            activation_function_value=self.activation_function_value,
454            name=self.name,
455            output_dimensions=self.this_output_dimensions,
456        )

Clear and reset dendrites before loading from a state dict.

Parameters
  • None
Returns
  • None
def set_this_output_dimensions(self, new_output_dimensions):
488    def set_this_output_dimensions(self, new_output_dimensions):
489        """Set the input dimensions for the neuron and dendrite blocks.
490
491        Signals to this NeuronModule that its input dimensions are different
492        than the global default.
493
494        Parameters
495        ----------
496        new_output_dimensions : list
497            A list or tensor specifying the new input dimensions.
498        Returns
499        -------
500        None
501
502        """
503        if type(new_output_dimensions) is list:
504            new_output_dimensions = torch.tensor(new_output_dimensions)
505        delattr(self, "this_output_dimensions")
506        self.register_buffer(
507            "this_output_dimensions", new_output_dimensions.detach().clone()
508        )
509        if (new_output_dimensions == 0).sum() != 1:
510            print(f"6 need exactly one 0 in the input dimensions: {self.name}")
511            print(new_output_dimensions)
512        self.this_node_index.copy_(
513            (new_output_dimensions == 0).nonzero(as_tuple=True)[0][0]
514        )
515        self.dendrite_module.set_this_output_dimensions(new_output_dimensions)

Set the input dimensions for the neuron and dendrite blocks.

Signals to this NeuronModule that its input dimensions are different than the global default.

Parameters
  • new_output_dimensions (list): A list or tensor specifying the new input dimensions.
Returns
  • None
def set_mode(self, mode):
517    def set_mode(self, mode):
518        """Switch between neuron training and dendrite training.
519
520        Parameters
521        ----------
522        mode : str
523            The mode to set. Either "n" for neuron training or "p" for pai-dendrite training.
524
525        Returns
526        -------
527        bool
528            True if mode was set successfully, False otherwise.
529
530        Notes
531        -----
532        If False is returned, the mode was not changed due to an error.
533        This is a problem that should not be ignored, but it can be ignored
534        by calling PGA.pc.set_checked_skipped_modules(True)
535        """
536
537        if self.module_config.get_verbose():
538            print(f"{self.name} calling set mode {mode}")
539        # If returning to neuron training
540        if mode == "n":
541            self.dendrite_module.set_mode(mode)
542            # Initialize the dendrite to neuron connections
543            if self.dendrite_modules_added > 0:
544                if self.module_config.get_learn_dendrites_live():
545                    values = torch.cat(
546                        (
547                            self.dendrites_to_top[self.dendrite_modules_added - 1],
548                            nn.Parameter(
549                                self.candidate_to_top.detach()
550                                .clone()
551                                .to(dtype=self.module_config.get_d_type())
552                            ),
553                        ),
554                        0,
555                    )
556                else:
557                    values = torch.cat(
558                        (
559                            self.dendrites_to_top[self.dendrite_modules_added - 1],
560                            nn.Parameter(
561                                torch.zeros(
562                                    (1, self.out_channels),
563                                    device=self.dendrites_to_top[
564                                        self.dendrite_modules_added - 1
565                                    ].device,
566                                    dtype=self.module_config.get_d_type(),
567                                )
568                            ),
569                        ),
570                        0,
571                    )
572                self.dendrites_to_top.append(
573                    nn.Parameter(
574                        values.detach()
575                        .clone()
576                        .to(
577                            device=self.module_config.get_device(),
578                            dtype=self.module_config.get_d_type(),
579                        ),
580                        requires_grad=True,
581                    )
582                )
583            else:
584                if self.module_config.get_learn_dendrites_live():
585                    self.dendrites_to_top.append(
586                        nn.Parameter(
587                            self.candidate_to_top.detach()
588                            .clone()
589                            .to(dtype=self.module_config.get_d_type()),
590                            requires_grad=True,
591                        )
592                    )
593                else:
594                    self.dendrites_to_top.append(
595                        nn.Parameter(
596                            torch.zeros(
597                                (1, self.out_channels),
598                                device=self.module_config.get_device(),
599                                dtype=self.module_config.get_d_type(),
600                            )
601                            .detach()
602                            .clone(),
603                            requires_grad=True,
604                        )
605                    )
606            self.dendrite_modules_added += 1
607            if self.module_config.get_perforated_backpropagation():
608                MPB.set_module_n_pb(self)
609                MPB.set_neuron_parameters(self.dendrites_to_top)
610
611        # If starting dendrite training
612        else:
613            try:
614                # Save the values that were calculated in filter_backward
615                self.out_channels = self.dendrite_module.dendrite_values[0].out_channels
616                self.dendrite_module.out_channels = (
617                    self.dendrite_module.dendrite_values[0].out_channels
618                )
619            except Exception as e:
620                print(e)
621                print(
622                    f"this occurred in module: {self.dendrite_module.dendrite_values[0].layer_name}"
623                )
624                print(
625                    "Module should be added to module_names_to_track so it doesn't have dendrites added"
626                )
627                print("If you are getting here but out_channels has not been set")
628                print(
629                    "A common reason is that this module never had gradients flow through it."
630                )
631                print("I have seen this happen because:")
632                print("-The weights were frozen (requires_grad = False)")
633                print(
634                    "-A model is added but not used so it was converted to a perforated module initialized"
635                )
636                print(
637                    "-A module was converted that doesn't have weights that get modified so backward doesn't flow through it"
638                )
639                print(
640                    "If this is normal behavior set GPA.pc.set_checked_skipped_modules(True) in the main to ignore"
641                )
642                print(
643                    "You can also set right now in this pdb terminal to have this not happen more after checking all modules this cycle."
644                )
645                if not self.module_config.get_checked_skipped_modules():
646                    pdb.set_trace()
647                return False
648            # Only change mode if it makes it past the above exception
649            self.dendrite_module.set_mode(mode)
650            if self.module_config.get_perforated_backpropagation():
651                MPB.set_module_p_pb(self)
652        return True

Switch between neuron training and dendrite training.

Parameters
  • mode (str): The mode to set. Either "n" for neuron training or "p" for pai-dendrite training.
Returns
  • bool: True if mode was set successfully, False otherwise.
Notes

If False is returned, the mode was not changed due to an error. This is a problem that should not be ignored, but it can be ignored by calling PGA.pc.set_checked_skipped_modules(True)

def create_new_dendrite_module(self):
654    def create_new_dendrite_module(self):
655        """Add an additional dendrite module.
656
657        Parameters
658        ----------
659        None
660
661        Returns
662        -------
663        None
664        """
665        self.dendrite_module.create_new_dendrite_module(self.main_module)

Add an additional dendrite module.

Parameters
  • None
Returns
  • None
def forward(self, *args, **kwargs):
667    def forward(self, *args, **kwargs):
668        """Forward pass through the neuron module.
669
670        Parameters
671        ----------
672        *args : tuple
673            Positional arguments for the forward pass.
674        **kwargs : dict
675            Keyword arguments for the forward pass.
676
677        Returns
678        -------
679        Any
680            The output of the module after processing through the neuron and dendrite modules.
681
682        Notes
683        -----
684            The output of this forward function will have the same format as the output
685            of the original module
686        """
687
688        # If debugging all input dimensions, quit program on first forward call
689        if self.module_config.get_debugging_output_dimensions() == 2:
690            print("all input dim problems now printed")
691            sys.exit(0)
692        if self.module_config.get_extra_verbose():
693            print(f"{self.name} calling forward")
694        # Call the main modules forward
695        out = self.main_module(*args, **kwargs)
696        # Filter with the processor if required
697        if self.processor is not None:
698            try:
699                out = self.processor.post_n1(out)
700            except Exception as e:
701                traceback.print_exc(limit=None, chain=True)
702                print(f"Your post_n1 processor for {self.name} caused this error")
703                print(
704                    f"You must check how this is defined and ensure that it is properly"
705                )
706                print(f"accepting outputs from the neuron module and returning the")
707                print(f"single tensor to be combined with the dendrites output tensor")
708                sys.exit()
709        # Call the forwards for all of the Dendrites
710        (
711            dendrite_outs,
712            candidate_outs,
713            candidate_nonlinear_outs,
714            candidate_outs_non_zeroed,
715        ) = self.dendrite_module(*args, **kwargs)
716        # If there are dendrites add all of their outputs to the neurons output
717        if self.dendrite_modules_added > 0:
718            for i in range(0, self.dendrite_modules_added):
719                to_top = self.dendrites_to_top[self.dendrite_modules_added - 1][i, :]
720                for dim in range(len(dendrite_outs[i].shape)):
721                    if dim == self.this_node_index:
722                        continue
723                    to_top = to_top.unsqueeze(dim)
724                if self.module_config.get_confirm_correct_sizes():
725                    to_top = to_top.expand(
726                        list(dendrite_outs[i].size())[0 : self.this_node_index]
727                        + [self.out_channels]
728                        + list(dendrite_outs[i].size())[self.this_node_index + 1 :]
729                    )
730                out = out + (dendrite_outs[i].to(out.device) * to_top.to(out.device))
731
732        # If learning live, add the candidate's output to the neuron's output via the live weight
733        if self.module_config.get_perforated_backpropagation():
734            out = MPB.apply_live_candidate_to_output(
735                self, out, candidate_nonlinear_outs
736            )
737
738        # Catch if processors are required
739        if type(out) is tuple:
740            print(self)
741            print(
742                f"The output of the above module {self.name} is a tuple when it must be a single tensor"
743            )
744            print(
745                "This must be fixed to enable the dendrite and neuron output to be combined"
746            )
747            print(
748                "Look in the API customization.md at section 2.2 regarding processors to fix this."
749            )
750            pdb.set_trace()
751
752        # Call filter backward to ensure the neuron index is setup correctly
753        if out.requires_grad:
754            out.register_hook(
755                lambda grad: filter_backward(grad, self.dendrite_module.dendrite_values)
756            )
757
758        # If there is a processor apply the second neuron stage
759        if self.processor is not None:
760            try:
761                out = self.processor.post_n2(out)
762            except Exception as e:
763                traceback.print_exc(limit=None, chain=True)
764                print(f"Your post_n2 processor for {self.name} caused this error")
765                print(
766                    f"You must check how this is defined and ensure that it is properly"
767                )
768                print(
769                    f"accepting the output tensor after combining the neuron's output "
770                )
771                print(f"with the dendrite's output and returning something that is the")
772                print(f"same format as your original module's return")
773                sys.exit()
774        return out

Forward pass through the neuron module.

Parameters
  • *args (tuple): Positional arguments for the forward pass.
  • **kwargs (dict): Keyword arguments for the forward pass.
Returns
  • Any: The output of the module after processing through the neuron and dendrite modules.
Notes

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

class TrackedNeuronModule(torch.nn.modules.module.Module):
777class TrackedNeuronModule(nn.Module):
778    """Wrapper for modules you don't want to add dendrites to. Ensures all modules are accounted for."""
779
780    def __init__(self, start_module, name):
781        """Initialize TrackedNeuronModule.
782
783        This function sets up the tracked neuron module to wrap the start_module
784        without adding dendrites.
785
786        Parameters
787        ----------
788        start_module : nn.Module
789            The module to wrap.
790        name : str
791            The name of the neuron module.
792        """
793        super(TrackedNeuronModule, self).__init__()
794
795        if isinstance(start_module, nn.Module):
796            self.main_module = start_module
797        else:
798            print("start_module must be nn.Module: %s" % name)
799            print(type(start_module))
800            print(start_module)
801            sys.exit(-1)
802        self.name = name
803
804        self.type = "tracked_module"
805        set_tracked_params(self.main_module)
806        if GPA.pc.get_verbose():
807            print(
808                f"tracking a module {self.name} with main type {type(self.main_module)}"
809            )
810            print(start_module)
811        GPA.pai_tracker.add_tracked_neuron_module(self)
812        if GPA.pc.get_perforated_backpropagation():
813            MPB.set_neuron_parameters(self.main_module)
814
815    def __getattr__(self, name):
816        """Get member variables from the main module.
817
818        Parameters
819        ----------
820        name : str
821            The name of the variable to retrieve.
822        Returns
823        -------
824        The requested variable.
825
826        Notes
827        -----
828        This method first attempts to retrieve the attribute from the PAINeuronModule instance.
829        If it fails, it tries to get the attribute from the wrapped main_module.
830        This allows seamless access to the main module's attributes without modifying original code.
831        """
832        try:
833            return super().__getattr__(name)
834        except AttributeError:
835            return getattr(self.main_module, name)
836
837    def __getitem__(self, index):
838        """Support indexing operations on the main module.
839
840        Parameters
841        ----------
842        index : int or slice
843            The index or slice to retrieve.
844
845        Returns
846        -------
847        The indexed item from the main module.
848        """
849        return self.main_module[index]
850
851    def set_mode(self, mode):
852        """Set mode for tracked module.
853
854        Parameters
855        ----------
856        mode : str
857            The mode to set. Either "n" for neuron training or "p" for pai-dendrite training.
858
859        Returns
860        -------
861        bool
862            True.
863
864        Notes
865        -----
866        This function does not change any behavior since this is a tracked module.
867        """
868
869        if GPA.pc.get_verbose():
870            print(f"{self.name} calling set mode {mode}")
871        return True
872
873    def forward(self, *args, **kwargs):
874        """Forward pass for tracked module.
875
876        Parameters
877        ----------
878        *args : tuple
879            Positional arguments for the forward pass.
880        **kwargs : dict
881            Keyword arguments for the forward pass.
882
883        Returns
884        -------
885        Any
886            The output of the module
887
888        Notes
889        -----
890            The output of this forward function will have the same format as the output
891            of the original module
892        """
893        return self.main_module(*args, **kwargs)
894
895    def __str__(self):
896        """String representation of the module.
897
898        Parameters
899        ----------
900        None
901
902        Returns
903        -------
904        str
905            String representation of the module.
906
907        Notes
908        -----
909        Setting for verbose changes level of details in the string output.
910        """
911
912        if GPA.pc.get_verbose():
913            total_string = self.main_module.__str__()
914            total_string = "PAITrackedModule(" + total_string + ")"
915            return total_string
916        else:
917            total_string = self.main_module.__str__()
918            total_string = "PAITrackedModule(" + total_string + ")"
919            return total_string
920
921    def __repr__(self):
922        """Representation of the module."""
923        return self.__str__()

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

TrackedNeuronModule(start_module, name)
780    def __init__(self, start_module, name):
781        """Initialize TrackedNeuronModule.
782
783        This function sets up the tracked neuron module to wrap the start_module
784        without adding dendrites.
785
786        Parameters
787        ----------
788        start_module : nn.Module
789            The module to wrap.
790        name : str
791            The name of the neuron module.
792        """
793        super(TrackedNeuronModule, self).__init__()
794
795        if isinstance(start_module, nn.Module):
796            self.main_module = start_module
797        else:
798            print("start_module must be nn.Module: %s" % name)
799            print(type(start_module))
800            print(start_module)
801            sys.exit(-1)
802        self.name = name
803
804        self.type = "tracked_module"
805        set_tracked_params(self.main_module)
806        if GPA.pc.get_verbose():
807            print(
808                f"tracking a module {self.name} with main type {type(self.main_module)}"
809            )
810            print(start_module)
811        GPA.pai_tracker.add_tracked_neuron_module(self)
812        if GPA.pc.get_perforated_backpropagation():
813            MPB.set_neuron_parameters(self.main_module)

Initialize TrackedNeuronModule.

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 set_mode(self, mode):
851    def set_mode(self, mode):
852        """Set mode for tracked module.
853
854        Parameters
855        ----------
856        mode : str
857            The mode to set. Either "n" for neuron training or "p" for pai-dendrite training.
858
859        Returns
860        -------
861        bool
862            True.
863
864        Notes
865        -----
866        This function does not change any behavior since this is a tracked module.
867        """
868
869        if GPA.pc.get_verbose():
870            print(f"{self.name} calling set mode {mode}")
871        return True

Set mode for tracked module.

Parameters
  • mode (str): The mode to set. Either "n" for neuron training or "p" for pai-dendrite training.
Returns
  • bool: True.
Notes

This function does not change any behavior since this is a tracked module.

def forward(self, *args, **kwargs):
873    def forward(self, *args, **kwargs):
874        """Forward pass for tracked module.
875
876        Parameters
877        ----------
878        *args : tuple
879            Positional arguments for the forward pass.
880        **kwargs : dict
881            Keyword arguments for the forward pass.
882
883        Returns
884        -------
885        Any
886            The output of the module
887
888        Notes
889        -----
890            The output of this forward function will have the same format as the output
891            of the original module
892        """
893        return self.main_module(*args, **kwargs)

Forward pass for tracked module.

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

def init_params(module, neuron_main_module):
926def init_params(module, neuron_main_module):
927    """Randomize weights after duplicating the main module for the next set of dendrites.
928
929    Parameters
930    ----------
931    module : nn.Module
932        The new dendrite module to initialize.
933    neuron_main_module : nn.Module
934        The main module of the neuron for potential weight scaling.
935
936    """
937    for param in module.parameters():
938        if param.dtype == torch.uint8:
939            param.data = torch.randint(0, 256, param.size(), dtype=torch.uint8)
940        else:
941            # If factoring in the main modules weights multiply the randn()
942            #  by the average abs value of the main modules weights
943            if GPA.pc.get_candidate_weight_init_by_main():
944                main_module_abs = 0
945                total_main_params = 0
946                for main_param in neuron_main_module.parameters():
947                    main_module_abs += main_param.abs().sum().item()
948                    total_main_params += main_param.numel()
949                if total_main_params > 0:
950                    main_module_abs /= total_main_params
951                else:
952                    main_module_abs = 1.0
953                multiplier = main_module_abs
954            else:
955                multiplier = 1.0
956            param.data = (
957                torch.randn(param.size(), dtype=param.dtype)
958                * GPA.pc.get_candidate_weight_initialization_multiplier()
959                * multiplier
960            )

Randomize weights after duplicating the main module for the next set of dendrites.

Parameters
  • module (nn.Module): The new dendrite module to initialize.
  • neuron_main_module (nn.Module): The main module of the neuron for potential weight scaling.
class PAIDendriteModule(torch.nn.modules.module.Module):
 963class PAIDendriteModule(nn.Module):
 964    """Module containing all dendrites modules added to the neuron module."""
 965
 966    def __init__(
 967        self,
 968        initial_module,
 969        activation_function_value=0.3,
 970        name="no_name_given",
 971        output_dimensions=None,
 972    ):
 973        """Initialize PAINeuronModule.
 974
 975        This function sets up the dendrite module to create candidate and permanent
 976        dendrite modules based on the initial_module provided.
 977
 978        Parameters
 979        ----------
 980        initial_module : nn.Module
 981            The module to copy.
 982        activation_function_value : float, optional
 983            A value associated with the activation function, by default 0.3.
 984        name : str
 985            The name of the neuron module.
 986        output_dimensions : vector, optional
 987            The dimensions of the input vector
 988        """
 989        super(PAIDendriteModule, self).__init__()
 990
 991        if output_dimensions is None:
 992            output_dimensions = []
 993
 994        self.layers = nn.ModuleList([])
 995        self.processors = []
 996        self.candidate_processors = []
 997        self.num_dendrites = 0
 998        # Number of dendrite cycles performed
 999        self.register_buffer(
1000            "num_cycles",
1001            torch.zeros(1, device=GPA.pc.get_device(), dtype=GPA.pc.get_d_type()),
1002        )
1003        self.mode = "n"
1004        self.name = name
1005        # Create a copy of the parent module so you don't have a pointer to the real one which causes save errors
1006        self.parent_module = UPA.deep_copy_pai(initial_module)
1007        if GPA.pc.get_perforated_backpropagation():
1008            MPB.set_ignored_parameters(self.parent_module)
1009        # Setup the input dimensions and node index for combining dendrite outputs
1010        if GPA.pc.get_perforated_backpropagation():
1011            MPB.create_extra_tensors(self)
1012        if output_dimensions == []:
1013            self.register_buffer(
1014                "this_output_dimensions", torch.tensor(GPA.pc.get_output_dimensions())
1015            )
1016        else:
1017            self.register_buffer(
1018                "this_output_dimensions", output_dimensions.detach().clone()
1019            )
1020        if (self.this_output_dimensions == 0).sum() != 1:
1021            print(f"1 need exactly one 0 in the input dimensions: {self.name}")
1022            print(self.this_output_dimensions)
1023            sys.exit(-1)
1024        self.register_buffer(
1025            "this_node_index", torch.tensor(GPA.pc.get_output_dimensions().index(0))
1026        )
1027
1028        # Initialize dendrite to dendrite connections
1029        self.dendrites_to_candidates = nn.ParameterList()
1030        self.dendrites_to_dendrites = nn.ParameterList()
1031
1032        # Store an activation function value if required
1033        self.activation_function_value = activation_function_value
1034        self.dendrite_values = nn.ModuleList([])
1035        for j in range(0, GPA.pc.get_global_candidates()):
1036            if GPA.pc.get_verbose():
1037                print(f"creating dendrite Values for {self.name}")
1038            self.dendrite_values.append(
1039                DendriteValueTracker(
1040                    False,
1041                    self.activation_function_value,
1042                    self.name,
1043                    self.this_output_dimensions,
1044                )
1045            )
1046        if GPA.pc.get_perforated_backpropagation():
1047            self.apply_pb_grads = MPB.apply_pb_grads.__get__(self, type(self))
1048            self.apply_pb_zero = MPB.apply_pb_zero.__get__(self, type(self))
1049
1050    def set_this_output_dimensions(self, new_output_dimensions):
1051        """Set input dimensions for dendrite module.
1052
1053        Signals to this DendriteModule that its input dimensions are different
1054        than the global default.
1055
1056        Parameters
1057        ----------
1058        new_output_dimensions : list
1059            A list or tensor specifying the new input dimensions.
1060        Returns
1061        -------
1062        None
1063
1064        """
1065
1066        if type(new_output_dimensions) is list:
1067            new_output_dimensions = torch.tensor(new_output_dimensions)
1068        delattr(self, "this_output_dimensions")
1069        self.register_buffer(
1070            "this_output_dimensions", new_output_dimensions.detach().clone()
1071        )
1072        if (new_output_dimensions == 0).sum() != 1:
1073            print(f"2 Need exactly one 0 in the input dimensions: {self.name}")
1074            print(new_output_dimensions)
1075            sys.exit(-1)
1076        self.this_node_index.copy_(
1077            (new_output_dimensions == 0).nonzero(as_tuple=True)[0][0]
1078        )
1079        for j in range(0, GPA.pc.get_global_candidates()):
1080            self.dendrite_values[j].set_this_output_dimensions(new_output_dimensions)
1081
1082    def create_new_dendrite_module(self, neuron_main_module):
1083        """Add a new set of dendrites."""
1084        # Candidate module
1085        self.candidate_module = nn.ModuleList([])
1086        # Copy that is unused for open source version
1087        self.best_candidate_module = nn.ModuleList([])
1088        if GPA.pc.get_verbose():
1089            print(self.name)
1090            print("Setting candidate processors")
1091        self.candidate_processors = []
1092        with torch.no_grad():
1093            for i in range(0, GPA.pc.get_global_candidates()):
1094
1095                new_module = UPA.deep_copy_pai(self.parent_module)
1096                init_params(new_module, neuron_main_module)
1097                self.candidate_module.append(new_module)
1098                self.best_candidate_module.append(UPA.deep_copy_pai(new_module))
1099                if type(self.parent_module) in GPA.pc.get_modules_with_processing():
1100                    module_index = GPA.pc.get_modules_with_processing().index(
1101                        type(self.parent_module)
1102                    )
1103                    self.candidate_processors.append(
1104                        GPA.pc.get_modules_processing_classes()[module_index]()
1105                    )
1106                elif (
1107                    type(self.parent_module).__name__
1108                    in GPA.pc.get_module_names_with_processing()
1109                ):
1110                    module_index = GPA.pc.get_module_names_with_processing().index(
1111                        type(self.parent_module).__name__
1112                    )
1113                    self.candidate_processors.append(
1114                        GPA.pc.get_module_by_name_processing_classes()[module_index]()
1115                    )
1116                if GPA.pc.get_perforated_backpropagation():
1117                    MPB.set_candidate_parameters(self.candidate_module[i])
1118                    MPB.set_ignored_parameters(self.best_candidate_module[i])
1119
1120        for i in range(0, GPA.pc.get_global_candidates()):
1121            self.candidate_module[i].to(GPA.pc.get_device())
1122            self.best_candidate_module[i].to(GPA.pc.get_device())
1123
1124        # Reset the dendrite_values objects
1125        for j in range(0, GPA.pc.get_global_candidates()):
1126            self.dendrite_values[j].reinitialize_for_pai()
1127
1128        # If there are already dendrites initialize the dendrite to dendrite connections
1129        if self.num_dendrites > 0:
1130            self.dendrites_to_candidates = nn.ParameterList()
1131            for j in range(0, GPA.pc.get_global_candidates()):
1132                self.dendrites_to_candidates.append(
1133                    nn.Parameter(
1134                        torch.zeros(
1135                            (self.num_dendrites, self.out_channels),
1136                            device=GPA.pc.get_device(),
1137                            dtype=GPA.pc.get_d_type(),
1138                        ),
1139                        requires_grad=True,
1140                    )
1141                )
1142                if GPA.pc.get_perforated_backpropagation():
1143                    MPB.init_candidates(self, j)
1144            if GPA.pc.get_perforated_backpropagation():
1145                MPB.set_candidate_parameters(self.dendrites_to_candidates)
1146            # Initialize best_dendrites_to_candidates_saved to snapshot peak-correlation weights at epoch boundaries
1147            self.best_dendrites_to_candidates_saved = []
1148            for j in range(0, GPA.pc.get_global_candidates()):
1149                self.best_dendrites_to_candidates_saved.append(
1150                    torch.zeros(
1151                        (self.num_dendrites, self.out_channels),
1152                        device=GPA.pc.get_device(),
1153                        dtype=GPA.pc.get_d_type(),
1154                    )
1155                )
1156
1157    def clear_processors(self):
1158        """Clear processors."""
1159        for processor in self.processors:
1160            if not processor:
1161                continue
1162            else:
1163                processor.clear_processor()
1164        for processor in self.candidate_processors:
1165            if not processor:
1166                continue
1167            else:
1168                processor.clear_processor()
1169
1170    def set_mode(self, mode):
1171        """Perform actions when switching between neuron and dendrite training.
1172
1173        Parameters
1174        ----------
1175        mode : str
1176            The mode to set. Either "n" for neuron training or "p" for pai-dendrite training.
1177
1178        Returns
1179        -------
1180        None
1181        """
1182
1183        self.mode = mode
1184        self.num_cycles += 1
1185        if GPA.pc.get_verbose():
1186            print(f"PAI calling set mode {mode} : {self.num_cycles}")
1187        print(f"Module {self.name} calling set mode {mode} : {self.num_cycles}")
1188        # When switching back to neuron training mode convert candidates modules into accepted modules
1189        if mode == "n":
1190            if GPA.pc.get_verbose():
1191                print("So calling all the things to add to modules")
1192            # Copy weights/bias from correct candidates
1193            if self.num_dendrites == 1:
1194                self.dendrites_to_dendrites = nn.ParameterList()
1195                self.dendrites_to_dendrites.append(torch.tensor([]))
1196            if self.num_dendrites >= 1:
1197                self.dendrites_to_dendrites.append(
1198                    torch.nn.Parameter(
1199                        torch.zeros(
1200                            [self.num_dendrites, self.out_channels],
1201                            device=GPA.pc.get_device(),
1202                            dtype=GPA.pc.get_d_type(),
1203                        ),
1204                        # Grad is true if not pb or if pb and dendrite_update_mode is true
1205                        requires_grad=(not GPA.pc.get_perforated_backpropagation())
1206                        or GPA.pc.get_dendrite_update_mode(),
1207                    )
1208                )
1209            with torch.no_grad():
1210                if GPA.pc.get_global_candidates() > 1:
1211                    print(
1212                        "This was a flag that will be needed if using multiple candidates. "
1213                        "It's not set up yet but nice work finding it."
1214                    )
1215                    print(
1216                        "Note: with multiple candidates, best-score ranking in new_best() uses "
1217                        "unnormalized covariance (prev_dendrite_candidate_correlation) rather than "
1218                        "the normalized correlation coefficient. Candidates with larger output "
1219                        "magnitude will be favored regardless of true correlation quality. "
1220                        "Fix by tracking running sigma_V and sigma_E and dividing in new_best()."
1221                    )
1222                    pdb.set_trace()
1223                plane_max_index = 0
1224                self.layers.append(
1225                    UPA.deep_copy_pai(self.best_candidate_module[plane_max_index])
1226                )
1227                self.layers[self.num_dendrites].to(GPA.pc.get_device())
1228                if self.num_dendrites > 0:
1229                    self.dendrites_to_dendrites[self.num_dendrites].copy_(
1230                        self.best_dendrites_to_candidates_saved[plane_max_index]
1231                    )
1232                if type(self.parent_module) in GPA.pc.get_modules_with_processing():
1233                    self.processors.append(self.candidate_processors[plane_max_index])
1234                if (
1235                    type(self.parent_module).__name__
1236                    in GPA.pc.get_module_names_with_processing()
1237                ):
1238                    self.processors.append(self.candidate_processors[plane_max_index])
1239            if GPA.pc.get_perforated_backpropagation():
1240                MPB.set_pb_mode(self, mode)
1241            del self.candidate_module, self.best_candidate_module
1242
1243            self.num_dendrites += 1
1244            if GPA.pc.get_perforated_backpropagation():
1245                MPB.set_dendrite_parameters(self.dendrites_to_dendrites)
1246                MPB.set_dendrite_parameters(self.layers)
1247
1248    def forward(self, *args, **kwargs):
1249        """Forward pass for dendrite module.
1250
1251        Parameters
1252        ----------
1253        *args : tuple
1254            Positional arguments for the forward pass.
1255        **kwargs : dict
1256            Keyword arguments for the forward pass.
1257
1258        Returns
1259        -------
1260        Any
1261            The output of the module after processing through the neuron and dendrite modules.
1262        Any
1263            Remaining outputs are only used for Perforated Backpropagation.
1264        Any
1265            Remaining outputs are only used for Perforated Backpropagation.
1266        Any
1267            Remaining outputs are only used for Perforated Backpropagation.
1268
1269        Notes
1270        -----
1271        If using Perforated Backpropagation, the additional outputs will be moved around in
1272        this code but left unused and only passed into separate PB functions.
1273        """
1274
1275        outs = {}
1276
1277        # For all modules apply processors, call the modules, then apply post processors
1278        args2, kwargs2 = args, kwargs
1279        for c in range(0, self.num_dendrites):
1280            if GPA.pc.get_perforated_backpropagation():
1281                args2, kwargs2 = MPB.preprocess_pb(*args, **kwargs)
1282            if self.processors != []:
1283                try:
1284                    args2, kwargs2 = self.processors[c].pre_d(*args2, **kwargs2)
1285                except Exception as e:
1286                    traceback.print_exc(limit=None, chain=True)
1287                    print(f"Your pre_d processor for {self.name} caused this error")
1288                    print(
1289                        f"You must check how this is defined and ensure that it is properly"
1290                    )
1291                    print(
1292                        f"accepting inputs to the PAIModule and returning what will then be"
1293                    )
1294                    print(f"the input to the dendrite module")
1295                    sys.exit()
1296            out_values = self.layers[c](*args2, **kwargs2)
1297            if self.processors != []:
1298                try:
1299                    outs[c] = self.processors[c].post_d(out_values)
1300                except Exception as e:
1301                    traceback.print_exc(limit=None, chain=True)
1302                    print(f"Your post_d processor for {self.name} caused this error")
1303                    print(
1304                        f"You must check how this is defined and ensure that it is properly"
1305                    )
1306                    print(
1307                        f"accepting outputs from the dendrite module and returning the"
1308                    )
1309                    print(
1310                        f"single tensor to be combined with the neurons output tensor"
1311                    )
1312                    sys.exit()
1313            else:
1314                outs[c] = out_values
1315
1316        # Create dendrite outputs
1317        # Each dendrite has input from previously created dendrites
1318        # So activation is added before the nonlinearity is called
1319        view_tuple = []
1320        for out_index in range(0, self.num_dendrites):
1321            current_out = outs[out_index]
1322            view_tuple = []
1323            for dim in range(len(current_out.shape)):
1324                if dim == self.this_node_index:
1325                    view_tuple.append(-1)
1326                    continue
1327                view_tuple.append(1)
1328
1329            for in_index in range(0, out_index):
1330                if view_tuple == [
1331                    1
1332                ]:  # This is only the case when passing a single datapoint rather than a batch
1333                    current_out = (
1334                        current_out
1335                        + self.dendrites_to_dendrites[out_index][in_index, :].to(
1336                            current_out.device
1337                        )
1338                        * outs[in_index]
1339                    )
1340                else:
1341                    current_out = (
1342                        current_out
1343                        + self.dendrites_to_dendrites[out_index][in_index, :]
1344                        .view(view_tuple)
1345                        .to(current_out.device)
1346                        * outs[in_index]
1347                    )
1348            outs[out_index] = GPA.pc.get_pai_forward_function()(current_out)
1349        # Return a dict which has all dendritic outputs after the activation functions were called
1350        if GPA.pc.get_perforated_backpropagation():
1351            candidate_outs, candidate_nonlinear_outs, candidate_non_zeroed = (
1352                MPB.forward_candidates(self, view_tuple, outs, *args2, **kwargs2)
1353            )
1354        else:
1355            candidate_outs, candidate_nonlinear_outs, candidate_non_zeroed = (
1356                {},
1357                {},
1358                {},
1359            )
1360        return outs, candidate_outs, candidate_nonlinear_outs, candidate_non_zeroed

Module containing all dendrites modules added to the neuron module.

PAIDendriteModule( initial_module, activation_function_value=0.3, name='no_name_given', output_dimensions=None)
 966    def __init__(
 967        self,
 968        initial_module,
 969        activation_function_value=0.3,
 970        name="no_name_given",
 971        output_dimensions=None,
 972    ):
 973        """Initialize PAINeuronModule.
 974
 975        This function sets up the dendrite module to create candidate and permanent
 976        dendrite modules based on the initial_module provided.
 977
 978        Parameters
 979        ----------
 980        initial_module : nn.Module
 981            The module to copy.
 982        activation_function_value : float, optional
 983            A value associated with the activation function, by default 0.3.
 984        name : str
 985            The name of the neuron module.
 986        output_dimensions : vector, optional
 987            The dimensions of the input vector
 988        """
 989        super(PAIDendriteModule, self).__init__()
 990
 991        if output_dimensions is None:
 992            output_dimensions = []
 993
 994        self.layers = nn.ModuleList([])
 995        self.processors = []
 996        self.candidate_processors = []
 997        self.num_dendrites = 0
 998        # Number of dendrite cycles performed
 999        self.register_buffer(
1000            "num_cycles",
1001            torch.zeros(1, device=GPA.pc.get_device(), dtype=GPA.pc.get_d_type()),
1002        )
1003        self.mode = "n"
1004        self.name = name
1005        # Create a copy of the parent module so you don't have a pointer to the real one which causes save errors
1006        self.parent_module = UPA.deep_copy_pai(initial_module)
1007        if GPA.pc.get_perforated_backpropagation():
1008            MPB.set_ignored_parameters(self.parent_module)
1009        # Setup the input dimensions and node index for combining dendrite outputs
1010        if GPA.pc.get_perforated_backpropagation():
1011            MPB.create_extra_tensors(self)
1012        if output_dimensions == []:
1013            self.register_buffer(
1014                "this_output_dimensions", torch.tensor(GPA.pc.get_output_dimensions())
1015            )
1016        else:
1017            self.register_buffer(
1018                "this_output_dimensions", output_dimensions.detach().clone()
1019            )
1020        if (self.this_output_dimensions == 0).sum() != 1:
1021            print(f"1 need exactly one 0 in the input dimensions: {self.name}")
1022            print(self.this_output_dimensions)
1023            sys.exit(-1)
1024        self.register_buffer(
1025            "this_node_index", torch.tensor(GPA.pc.get_output_dimensions().index(0))
1026        )
1027
1028        # Initialize dendrite to dendrite connections
1029        self.dendrites_to_candidates = nn.ParameterList()
1030        self.dendrites_to_dendrites = nn.ParameterList()
1031
1032        # Store an activation function value if required
1033        self.activation_function_value = activation_function_value
1034        self.dendrite_values = nn.ModuleList([])
1035        for j in range(0, GPA.pc.get_global_candidates()):
1036            if GPA.pc.get_verbose():
1037                print(f"creating dendrite Values for {self.name}")
1038            self.dendrite_values.append(
1039                DendriteValueTracker(
1040                    False,
1041                    self.activation_function_value,
1042                    self.name,
1043                    self.this_output_dimensions,
1044                )
1045            )
1046        if GPA.pc.get_perforated_backpropagation():
1047            self.apply_pb_grads = MPB.apply_pb_grads.__get__(self, type(self))
1048            self.apply_pb_zero = MPB.apply_pb_zero.__get__(self, type(self))

Initialize PAINeuronModule.

This function sets up the dendrite module to create candidate and permanent dendrite modules based on the initial_module provided.

Parameters
  • initial_module (nn.Module): The module to copy.
  • activation_function_value (float, optional): A value associated with the activation function, by default 0.3.
  • name (str): The name of the neuron module.
  • output_dimensions (vector, optional): The dimensions of the input vector
layers
processors
candidate_processors
num_dendrites
mode
name
parent_module
dendrites_to_candidates
dendrites_to_dendrites
activation_function_value
dendrite_values
def set_this_output_dimensions(self, new_output_dimensions):
1050    def set_this_output_dimensions(self, new_output_dimensions):
1051        """Set input dimensions for dendrite module.
1052
1053        Signals to this DendriteModule that its input dimensions are different
1054        than the global default.
1055
1056        Parameters
1057        ----------
1058        new_output_dimensions : list
1059            A list or tensor specifying the new input dimensions.
1060        Returns
1061        -------
1062        None
1063
1064        """
1065
1066        if type(new_output_dimensions) is list:
1067            new_output_dimensions = torch.tensor(new_output_dimensions)
1068        delattr(self, "this_output_dimensions")
1069        self.register_buffer(
1070            "this_output_dimensions", new_output_dimensions.detach().clone()
1071        )
1072        if (new_output_dimensions == 0).sum() != 1:
1073            print(f"2 Need exactly one 0 in the input dimensions: {self.name}")
1074            print(new_output_dimensions)
1075            sys.exit(-1)
1076        self.this_node_index.copy_(
1077            (new_output_dimensions == 0).nonzero(as_tuple=True)[0][0]
1078        )
1079        for j in range(0, GPA.pc.get_global_candidates()):
1080            self.dendrite_values[j].set_this_output_dimensions(new_output_dimensions)

Set input dimensions for dendrite module.

Signals to this DendriteModule that its input dimensions are different than the global default.

Parameters
  • new_output_dimensions (list): A list or tensor specifying the new input dimensions.
Returns
  • None
def create_new_dendrite_module(self, neuron_main_module):
1082    def create_new_dendrite_module(self, neuron_main_module):
1083        """Add a new set of dendrites."""
1084        # Candidate module
1085        self.candidate_module = nn.ModuleList([])
1086        # Copy that is unused for open source version
1087        self.best_candidate_module = nn.ModuleList([])
1088        if GPA.pc.get_verbose():
1089            print(self.name)
1090            print("Setting candidate processors")
1091        self.candidate_processors = []
1092        with torch.no_grad():
1093            for i in range(0, GPA.pc.get_global_candidates()):
1094
1095                new_module = UPA.deep_copy_pai(self.parent_module)
1096                init_params(new_module, neuron_main_module)
1097                self.candidate_module.append(new_module)
1098                self.best_candidate_module.append(UPA.deep_copy_pai(new_module))
1099                if type(self.parent_module) in GPA.pc.get_modules_with_processing():
1100                    module_index = GPA.pc.get_modules_with_processing().index(
1101                        type(self.parent_module)
1102                    )
1103                    self.candidate_processors.append(
1104                        GPA.pc.get_modules_processing_classes()[module_index]()
1105                    )
1106                elif (
1107                    type(self.parent_module).__name__
1108                    in GPA.pc.get_module_names_with_processing()
1109                ):
1110                    module_index = GPA.pc.get_module_names_with_processing().index(
1111                        type(self.parent_module).__name__
1112                    )
1113                    self.candidate_processors.append(
1114                        GPA.pc.get_module_by_name_processing_classes()[module_index]()
1115                    )
1116                if GPA.pc.get_perforated_backpropagation():
1117                    MPB.set_candidate_parameters(self.candidate_module[i])
1118                    MPB.set_ignored_parameters(self.best_candidate_module[i])
1119
1120        for i in range(0, GPA.pc.get_global_candidates()):
1121            self.candidate_module[i].to(GPA.pc.get_device())
1122            self.best_candidate_module[i].to(GPA.pc.get_device())
1123
1124        # Reset the dendrite_values objects
1125        for j in range(0, GPA.pc.get_global_candidates()):
1126            self.dendrite_values[j].reinitialize_for_pai()
1127
1128        # If there are already dendrites initialize the dendrite to dendrite connections
1129        if self.num_dendrites > 0:
1130            self.dendrites_to_candidates = nn.ParameterList()
1131            for j in range(0, GPA.pc.get_global_candidates()):
1132                self.dendrites_to_candidates.append(
1133                    nn.Parameter(
1134                        torch.zeros(
1135                            (self.num_dendrites, self.out_channels),
1136                            device=GPA.pc.get_device(),
1137                            dtype=GPA.pc.get_d_type(),
1138                        ),
1139                        requires_grad=True,
1140                    )
1141                )
1142                if GPA.pc.get_perforated_backpropagation():
1143                    MPB.init_candidates(self, j)
1144            if GPA.pc.get_perforated_backpropagation():
1145                MPB.set_candidate_parameters(self.dendrites_to_candidates)
1146            # Initialize best_dendrites_to_candidates_saved to snapshot peak-correlation weights at epoch boundaries
1147            self.best_dendrites_to_candidates_saved = []
1148            for j in range(0, GPA.pc.get_global_candidates()):
1149                self.best_dendrites_to_candidates_saved.append(
1150                    torch.zeros(
1151                        (self.num_dendrites, self.out_channels),
1152                        device=GPA.pc.get_device(),
1153                        dtype=GPA.pc.get_d_type(),
1154                    )
1155                )

Add a new set of dendrites.

def clear_processors(self):
1157    def clear_processors(self):
1158        """Clear processors."""
1159        for processor in self.processors:
1160            if not processor:
1161                continue
1162            else:
1163                processor.clear_processor()
1164        for processor in self.candidate_processors:
1165            if not processor:
1166                continue
1167            else:
1168                processor.clear_processor()

Clear processors.

def set_mode(self, mode):
1170    def set_mode(self, mode):
1171        """Perform actions when switching between neuron and dendrite training.
1172
1173        Parameters
1174        ----------
1175        mode : str
1176            The mode to set. Either "n" for neuron training or "p" for pai-dendrite training.
1177
1178        Returns
1179        -------
1180        None
1181        """
1182
1183        self.mode = mode
1184        self.num_cycles += 1
1185        if GPA.pc.get_verbose():
1186            print(f"PAI calling set mode {mode} : {self.num_cycles}")
1187        print(f"Module {self.name} calling set mode {mode} : {self.num_cycles}")
1188        # When switching back to neuron training mode convert candidates modules into accepted modules
1189        if mode == "n":
1190            if GPA.pc.get_verbose():
1191                print("So calling all the things to add to modules")
1192            # Copy weights/bias from correct candidates
1193            if self.num_dendrites == 1:
1194                self.dendrites_to_dendrites = nn.ParameterList()
1195                self.dendrites_to_dendrites.append(torch.tensor([]))
1196            if self.num_dendrites >= 1:
1197                self.dendrites_to_dendrites.append(
1198                    torch.nn.Parameter(
1199                        torch.zeros(
1200                            [self.num_dendrites, self.out_channels],
1201                            device=GPA.pc.get_device(),
1202                            dtype=GPA.pc.get_d_type(),
1203                        ),
1204                        # Grad is true if not pb or if pb and dendrite_update_mode is true
1205                        requires_grad=(not GPA.pc.get_perforated_backpropagation())
1206                        or GPA.pc.get_dendrite_update_mode(),
1207                    )
1208                )
1209            with torch.no_grad():
1210                if GPA.pc.get_global_candidates() > 1:
1211                    print(
1212                        "This was a flag that will be needed if using multiple candidates. "
1213                        "It's not set up yet but nice work finding it."
1214                    )
1215                    print(
1216                        "Note: with multiple candidates, best-score ranking in new_best() uses "
1217                        "unnormalized covariance (prev_dendrite_candidate_correlation) rather than "
1218                        "the normalized correlation coefficient. Candidates with larger output "
1219                        "magnitude will be favored regardless of true correlation quality. "
1220                        "Fix by tracking running sigma_V and sigma_E and dividing in new_best()."
1221                    )
1222                    pdb.set_trace()
1223                plane_max_index = 0
1224                self.layers.append(
1225                    UPA.deep_copy_pai(self.best_candidate_module[plane_max_index])
1226                )
1227                self.layers[self.num_dendrites].to(GPA.pc.get_device())
1228                if self.num_dendrites > 0:
1229                    self.dendrites_to_dendrites[self.num_dendrites].copy_(
1230                        self.best_dendrites_to_candidates_saved[plane_max_index]
1231                    )
1232                if type(self.parent_module) in GPA.pc.get_modules_with_processing():
1233                    self.processors.append(self.candidate_processors[plane_max_index])
1234                if (
1235                    type(self.parent_module).__name__
1236                    in GPA.pc.get_module_names_with_processing()
1237                ):
1238                    self.processors.append(self.candidate_processors[plane_max_index])
1239            if GPA.pc.get_perforated_backpropagation():
1240                MPB.set_pb_mode(self, mode)
1241            del self.candidate_module, self.best_candidate_module
1242
1243            self.num_dendrites += 1
1244            if GPA.pc.get_perforated_backpropagation():
1245                MPB.set_dendrite_parameters(self.dendrites_to_dendrites)
1246                MPB.set_dendrite_parameters(self.layers)

Perform actions when switching between neuron and dendrite training.

Parameters
  • mode (str): The mode to set. Either "n" for neuron training or "p" for pai-dendrite training.
Returns
  • None
def forward(self, *args, **kwargs):
1248    def forward(self, *args, **kwargs):
1249        """Forward pass for dendrite module.
1250
1251        Parameters
1252        ----------
1253        *args : tuple
1254            Positional arguments for the forward pass.
1255        **kwargs : dict
1256            Keyword arguments for the forward pass.
1257
1258        Returns
1259        -------
1260        Any
1261            The output of the module after processing through the neuron and dendrite modules.
1262        Any
1263            Remaining outputs are only used for Perforated Backpropagation.
1264        Any
1265            Remaining outputs are only used for Perforated Backpropagation.
1266        Any
1267            Remaining outputs are only used for Perforated Backpropagation.
1268
1269        Notes
1270        -----
1271        If using Perforated Backpropagation, the additional outputs will be moved around in
1272        this code but left unused and only passed into separate PB functions.
1273        """
1274
1275        outs = {}
1276
1277        # For all modules apply processors, call the modules, then apply post processors
1278        args2, kwargs2 = args, kwargs
1279        for c in range(0, self.num_dendrites):
1280            if GPA.pc.get_perforated_backpropagation():
1281                args2, kwargs2 = MPB.preprocess_pb(*args, **kwargs)
1282            if self.processors != []:
1283                try:
1284                    args2, kwargs2 = self.processors[c].pre_d(*args2, **kwargs2)
1285                except Exception as e:
1286                    traceback.print_exc(limit=None, chain=True)
1287                    print(f"Your pre_d processor for {self.name} caused this error")
1288                    print(
1289                        f"You must check how this is defined and ensure that it is properly"
1290                    )
1291                    print(
1292                        f"accepting inputs to the PAIModule and returning what will then be"
1293                    )
1294                    print(f"the input to the dendrite module")
1295                    sys.exit()
1296            out_values = self.layers[c](*args2, **kwargs2)
1297            if self.processors != []:
1298                try:
1299                    outs[c] = self.processors[c].post_d(out_values)
1300                except Exception as e:
1301                    traceback.print_exc(limit=None, chain=True)
1302                    print(f"Your post_d processor for {self.name} caused this error")
1303                    print(
1304                        f"You must check how this is defined and ensure that it is properly"
1305                    )
1306                    print(
1307                        f"accepting outputs from the dendrite module and returning the"
1308                    )
1309                    print(
1310                        f"single tensor to be combined with the neurons output tensor"
1311                    )
1312                    sys.exit()
1313            else:
1314                outs[c] = out_values
1315
1316        # Create dendrite outputs
1317        # Each dendrite has input from previously created dendrites
1318        # So activation is added before the nonlinearity is called
1319        view_tuple = []
1320        for out_index in range(0, self.num_dendrites):
1321            current_out = outs[out_index]
1322            view_tuple = []
1323            for dim in range(len(current_out.shape)):
1324                if dim == self.this_node_index:
1325                    view_tuple.append(-1)
1326                    continue
1327                view_tuple.append(1)
1328
1329            for in_index in range(0, out_index):
1330                if view_tuple == [
1331                    1
1332                ]:  # This is only the case when passing a single datapoint rather than a batch
1333                    current_out = (
1334                        current_out
1335                        + self.dendrites_to_dendrites[out_index][in_index, :].to(
1336                            current_out.device
1337                        )
1338                        * outs[in_index]
1339                    )
1340                else:
1341                    current_out = (
1342                        current_out
1343                        + self.dendrites_to_dendrites[out_index][in_index, :]
1344                        .view(view_tuple)
1345                        .to(current_out.device)
1346                        * outs[in_index]
1347                    )
1348            outs[out_index] = GPA.pc.get_pai_forward_function()(current_out)
1349        # Return a dict which has all dendritic outputs after the activation functions were called
1350        if GPA.pc.get_perforated_backpropagation():
1351            candidate_outs, candidate_nonlinear_outs, candidate_non_zeroed = (
1352                MPB.forward_candidates(self, view_tuple, outs, *args2, **kwargs2)
1353            )
1354        else:
1355            candidate_outs, candidate_nonlinear_outs, candidate_non_zeroed = (
1356                {},
1357                {},
1358                {},
1359            )
1360        return outs, candidate_outs, candidate_nonlinear_outs, candidate_non_zeroed

Forward pass for dendrite module.

Parameters
  • *args (tuple): Positional arguments for the forward pass.
  • **kwargs (dict): Keyword arguments for the forward pass.
Returns
  • Any: The output of the module after processing through the neuron and dendrite modules.
  • Any: Remaining outputs are only used for Perforated Backpropagation.
  • Any: Remaining outputs are only used for Perforated Backpropagation.
  • Any: Remaining outputs are only used for Perforated Backpropagation.
Notes

If using Perforated Backpropagation, the additional outputs will be moved around in this code but left unused and only passed into separate PB functions.

class DendriteValueTracker(torch.nn.modules.module.Module):
1363class DendriteValueTracker(nn.Module):
1364    """Tracker object that maintains certain values for each set of dendrites."""
1365
1366    def __init__(
1367        self,
1368        initialized,
1369        activation_function_value,
1370        name,
1371        output_dimensions,
1372        out_channels=-1,
1373    ):
1374        """Initialize DendriteValueTracker.
1375
1376        This function sets up the value tracker to maintain statistics and values
1377        for each set of dendrites.
1378
1379        Parameters
1380        ----------
1381        initialized : int
1382            Whether the dendrite has been initialized (1) or not (0).
1383        activation_function_value : float
1384            A value associated with the activation function.
1385        name : str
1386            The name of the associated neuron module.
1387        output_dimensions : vector
1388            The dimensions of the input vector.
1389        out_channels : int
1390            The number of output channels
1391        """
1392        super(DendriteValueTracker, self).__init__()
1393
1394        self.layer_name = name
1395        for val_name in DENDRITE_INIT_VALUES:
1396            self.register_buffer(
1397                val_name,
1398                torch.zeros(1, device=GPA.pc.get_device(), dtype=GPA.pc.get_d_type()),
1399            )
1400        self.initialized[0] = initialized
1401        self.activation_function_value = activation_function_value
1402        self.register_buffer(
1403            "this_output_dimensions", output_dimensions.clone().detach()
1404        )
1405        if (self.this_output_dimensions == 0).sum() != 1:
1406            print(f"3 need exactly one 0 in the input dimensions: {self.layer_name}")
1407            print(self.this_output_dimensions)
1408            sys.exit(-1)
1409        self.register_buffer(
1410            "this_node_index", (output_dimensions == 0).nonzero(as_tuple=True)[0]
1411        )
1412        if out_channels != -1:
1413            self.setup_arrays(out_channels)
1414        else:
1415            self.out_channels = -1
1416
1417    def print(self):
1418        """Print value tracker information."""
1419        total_string = "Value Tracker:"
1420        for val_name in DENDRITE_INIT_VALUES:
1421            total_string += f"\t{val_name}:\n\t\t"
1422            total_string += getattr(self, val_name).__repr__()
1423            total_string += "\n"
1424        for val_name in get_DENDRITE_TENSOR_VALUES():
1425            if getattr(self, val_name, None) is not None:
1426                total_string += f"\t{val_name}:\n\t\t"
1427                total_string += getattr(self, val_name).__repr__()
1428                total_string += "\n"
1429        print(total_string)
1430
1431    def set_this_output_dimensions(self, new_output_dimensions):
1432        """Set input dimensions for value tracker
1433
1434        Signals to this DendriteValueTracker that its input dimensions are different
1435        than the global default.
1436
1437        Parameters
1438        ----------
1439        new_output_dimensions : list
1440            A list or tensor specifying the new input dimensions.
1441        Returns
1442        -------
1443        None
1444
1445        """
1446        if type(new_output_dimensions) is list:
1447            new_output_dimensions = torch.tensor(new_output_dimensions)
1448        delattr(self, "this_output_dimensions")
1449        self.register_buffer(
1450            "this_output_dimensions", new_output_dimensions.detach().clone()
1451        )
1452        if (new_output_dimensions == 0).sum() != 1:
1453            print(f"4 need exactly one 0 in the input dimensions: {self.layer_name}")
1454            print(new_output_dimensions)
1455            sys.exit(-1)
1456        self.this_node_index.copy_(
1457            (new_output_dimensions == 0).nonzero(as_tuple=True)[0][0]
1458        )
1459
1460    def set_out_channels(self, shape_values):
1461        """Set output channels based on shape values and saved node index
1462
1463        Parameters
1464        ----------
1465        shape_values : list or torch.Size
1466            A list or tensor specifying the shape values.
1467
1468        Returns
1469        -------
1470        None
1471        """
1472        if type(shape_values) == torch.Size:
1473            self.out_channels = int(shape_values[self.this_node_index])
1474        else:
1475            self.out_channels = int(shape_values[self.this_node_index].item())
1476
1477    def setup_arrays(self, out_channels):
1478        """Setup arrays for value tracker.
1479
1480        Parameters
1481        ----------
1482        out_channels : int
1483            The number of output channels.
1484        Returns
1485        -------
1486        None
1487
1488        """
1489        self.out_channels = out_channels
1490        for val_name in get_DENDRITE_TENSOR_VALUES():
1491            self.register_buffer(
1492                val_name,
1493                torch.zeros(
1494                    out_channels, device=GPA.pc.get_device(), dtype=GPA.pc.get_d_type()
1495                ),
1496            )
1497
1498        for name in get_VALUE_TRACKER_ARRAYS():
1499            setattr(self, name, {})
1500            count = 1
1501            if torch.cuda.device_count() > count:
1502                count = torch.cuda.device_count()
1503            for i in range(count):
1504                getattr(self, name)[i] = []
1505        for val_name in get_DENDRITE_SINGLE_VALUES():
1506            self.register_buffer(
1507                val_name,
1508                torch.zeros(1, device=GPA.pc.get_device(), dtype=GPA.pc.get_d_type()),
1509            )
1510
1511    def reinitialize_for_pai(self):
1512        """Reinitialize value tracker to add the next set of dendrites"""
1513
1514        if self.out_channels == -1:
1515            print("You have a perforated module that was never initialized")
1516            print("This likely means it is not being added to the autograd graph")
1517            print("Check your forward function that it is actually being used")
1518            print("If its not you should really delete it, but you can also add")
1519            print(self.layer_name)
1520            print("with:")
1521            print("GPA.pc.append_module_ids_to_track(['" + self.layer_name + "'])")
1522            print("This can also happen while testing_dendrite_capacity if you")
1523            print(
1524                "run a validation cycle and try to add Dendrites before doing any training.\n"
1525            )
1526            pdb.set_trace()
1527
1528        self.initialized[0] = 0
1529        if GPA.pc.get_perforated_backpropagation():
1530            MPB.reinitialize_for_pb(self)
1531        else:
1532            for val_name in get_DENDRITE_REINIT_VALUES():
1533                setattr(self, val_name, getattr(self, val_name) * 0)

Tracker object that maintains certain values for each set of dendrites.

DendriteValueTracker( initialized, activation_function_value, name, output_dimensions, out_channels=-1)
1366    def __init__(
1367        self,
1368        initialized,
1369        activation_function_value,
1370        name,
1371        output_dimensions,
1372        out_channels=-1,
1373    ):
1374        """Initialize DendriteValueTracker.
1375
1376        This function sets up the value tracker to maintain statistics and values
1377        for each set of dendrites.
1378
1379        Parameters
1380        ----------
1381        initialized : int
1382            Whether the dendrite has been initialized (1) or not (0).
1383        activation_function_value : float
1384            A value associated with the activation function.
1385        name : str
1386            The name of the associated neuron module.
1387        output_dimensions : vector
1388            The dimensions of the input vector.
1389        out_channels : int
1390            The number of output channels
1391        """
1392        super(DendriteValueTracker, self).__init__()
1393
1394        self.layer_name = name
1395        for val_name in DENDRITE_INIT_VALUES:
1396            self.register_buffer(
1397                val_name,
1398                torch.zeros(1, device=GPA.pc.get_device(), dtype=GPA.pc.get_d_type()),
1399            )
1400        self.initialized[0] = initialized
1401        self.activation_function_value = activation_function_value
1402        self.register_buffer(
1403            "this_output_dimensions", output_dimensions.clone().detach()
1404        )
1405        if (self.this_output_dimensions == 0).sum() != 1:
1406            print(f"3 need exactly one 0 in the input dimensions: {self.layer_name}")
1407            print(self.this_output_dimensions)
1408            sys.exit(-1)
1409        self.register_buffer(
1410            "this_node_index", (output_dimensions == 0).nonzero(as_tuple=True)[0]
1411        )
1412        if out_channels != -1:
1413            self.setup_arrays(out_channels)
1414        else:
1415            self.out_channels = -1

Initialize DendriteValueTracker.

This function sets up the value tracker to maintain statistics and values for each set of dendrites.

Parameters
  • initialized (int): Whether the dendrite has been initialized (1) or not (0).
  • activation_function_value (float): A value associated with the activation function.
  • name (str): The name of the associated neuron module.
  • output_dimensions (vector): The dimensions of the input vector.
  • out_channels (int): The number of output channels
layer_name
activation_function_value
def print(self):
1417    def print(self):
1418        """Print value tracker information."""
1419        total_string = "Value Tracker:"
1420        for val_name in DENDRITE_INIT_VALUES:
1421            total_string += f"\t{val_name}:\n\t\t"
1422            total_string += getattr(self, val_name).__repr__()
1423            total_string += "\n"
1424        for val_name in get_DENDRITE_TENSOR_VALUES():
1425            if getattr(self, val_name, None) is not None:
1426                total_string += f"\t{val_name}:\n\t\t"
1427                total_string += getattr(self, val_name).__repr__()
1428                total_string += "\n"
1429        print(total_string)

Print value tracker information.

def set_this_output_dimensions(self, new_output_dimensions):
1431    def set_this_output_dimensions(self, new_output_dimensions):
1432        """Set input dimensions for value tracker
1433
1434        Signals to this DendriteValueTracker that its input dimensions are different
1435        than the global default.
1436
1437        Parameters
1438        ----------
1439        new_output_dimensions : list
1440            A list or tensor specifying the new input dimensions.
1441        Returns
1442        -------
1443        None
1444
1445        """
1446        if type(new_output_dimensions) is list:
1447            new_output_dimensions = torch.tensor(new_output_dimensions)
1448        delattr(self, "this_output_dimensions")
1449        self.register_buffer(
1450            "this_output_dimensions", new_output_dimensions.detach().clone()
1451        )
1452        if (new_output_dimensions == 0).sum() != 1:
1453            print(f"4 need exactly one 0 in the input dimensions: {self.layer_name}")
1454            print(new_output_dimensions)
1455            sys.exit(-1)
1456        self.this_node_index.copy_(
1457            (new_output_dimensions == 0).nonzero(as_tuple=True)[0][0]
1458        )

Set input dimensions for value tracker

Signals to this DendriteValueTracker that its input dimensions are different than the global default.

Parameters
  • new_output_dimensions (list): A list or tensor specifying the new input dimensions.
Returns
  • None
def set_out_channels(self, shape_values):
1460    def set_out_channels(self, shape_values):
1461        """Set output channels based on shape values and saved node index
1462
1463        Parameters
1464        ----------
1465        shape_values : list or torch.Size
1466            A list or tensor specifying the shape values.
1467
1468        Returns
1469        -------
1470        None
1471        """
1472        if type(shape_values) == torch.Size:
1473            self.out_channels = int(shape_values[self.this_node_index])
1474        else:
1475            self.out_channels = int(shape_values[self.this_node_index].item())

Set output channels based on shape values and saved node index

Parameters
  • shape_values (list or torch.Size): A list or tensor specifying the shape values.
Returns
  • None
def setup_arrays(self, out_channels):
1477    def setup_arrays(self, out_channels):
1478        """Setup arrays for value tracker.
1479
1480        Parameters
1481        ----------
1482        out_channels : int
1483            The number of output channels.
1484        Returns
1485        -------
1486        None
1487
1488        """
1489        self.out_channels = out_channels
1490        for val_name in get_DENDRITE_TENSOR_VALUES():
1491            self.register_buffer(
1492                val_name,
1493                torch.zeros(
1494                    out_channels, device=GPA.pc.get_device(), dtype=GPA.pc.get_d_type()
1495                ),
1496            )
1497
1498        for name in get_VALUE_TRACKER_ARRAYS():
1499            setattr(self, name, {})
1500            count = 1
1501            if torch.cuda.device_count() > count:
1502                count = torch.cuda.device_count()
1503            for i in range(count):
1504                getattr(self, name)[i] = []
1505        for val_name in get_DENDRITE_SINGLE_VALUES():
1506            self.register_buffer(
1507                val_name,
1508                torch.zeros(1, device=GPA.pc.get_device(), dtype=GPA.pc.get_d_type()),
1509            )

Setup arrays for value tracker.

Parameters
  • out_channels (int): The number of output channels.
Returns
  • None
def reinitialize_for_pai(self):
1511    def reinitialize_for_pai(self):
1512        """Reinitialize value tracker to add the next set of dendrites"""
1513
1514        if self.out_channels == -1:
1515            print("You have a perforated module that was never initialized")
1516            print("This likely means it is not being added to the autograd graph")
1517            print("Check your forward function that it is actually being used")
1518            print("If its not you should really delete it, but you can also add")
1519            print(self.layer_name)
1520            print("with:")
1521            print("GPA.pc.append_module_ids_to_track(['" + self.layer_name + "'])")
1522            print("This can also happen while testing_dendrite_capacity if you")
1523            print(
1524                "run a validation cycle and try to add Dendrites before doing any training.\n"
1525            )
1526            pdb.set_trace()
1527
1528        self.initialized[0] = 0
1529        if GPA.pc.get_perforated_backpropagation():
1530            MPB.reinitialize_for_pb(self)
1531        else:
1532            for val_name in get_DENDRITE_REINIT_VALUES():
1533                setattr(self, val_name, getattr(self, val_name) * 0)

Reinitialize value tracker to add the next set of dendrites