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