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