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