perforatedai.library_perforatedai
1# Copyright (c) 2025 Perforated AI 2 3import math 4import pdb 5from itertools import chain 6 7import torch 8import torch.nn as nn 9import torch.nn.init as init 10import torch.nn.functional as F 11import torchvision.models.resnet as resnet_pt 12from abc import ABC, abstractmethod 13 14from perforatedai import globals_perforatedai as GPA 15 16""" 17Details on processors can be found in customization.md in the API directory. 18 19They exist to enable simplicity in adding dendrites to modules where 20forward() is not one tensor in and one tensor out. 21 22The main module has one instance, which uses post_n1 and post_n2 23and each new Dendrite node gets a unique instance to use pre_d and post_d. 24""" 25 26 27class PAIProcessor(ABC): 28 """ 29 Abstract base class for processing neuron and dendrite operations. 30 31 Processors handle state management and data flow between neurons and 32 dendrites, allowing for custom pre/post processing of modules which have 33 multiple inputs and outputs, rather than the default single tensor input/output. 34 Subclasses should implement the five core processing methods to handle 35 their specific state management needs. 36 """ 37 38 @abstractmethod 39 def post_n1(self, *args, **kwargs): 40 """ 41 Post-process neuron output before dendrite processing. 42 43 Called immediately after the main module/neuron is executed and before 44 any dendrite processing occurs. This method should extract and return 45 only the tensor of the neuron output that should be seen by 46 dendrite operations. 47 48 Parameters 49 ---------- 50 *args : tuple 51 Positional arguments, typically containing the neuron output. 52 **kwargs : dict 53 Keyword arguments from the neuron output. 54 55 Returns 56 ------- 57 Any 58 The filtered output to be passed to dendrite processing. 59 """ 60 pass 61 62 @abstractmethod 63 def post_n2(self, *args, **kwargs): 64 """ 65 Post-process dendrite-modified output before final return. 66 67 Called after dendrite processing is complete and before passing the 68 final value forward in the network. This method should combine the 69 dendrite-modified output with any stored state to produce the complete 70 output that matches the expected format of the main module. 71 72 Parameters 73 ---------- 74 *args : tuple 75 Positional arguments containing the dendrite-modified output. 76 **kwargs : dict 77 Keyword arguments from the processing chain. 78 79 Returns 80 ------- 81 Any 82 The complete output in the format expected by downstream components. 83 """ 84 pass 85 86 @abstractmethod 87 def pre_d(self, *args, **kwargs): 88 """ 89 Pre-process input before dendrite operations. 90 91 Filters and prepares inputs for dendrite processing. This method handles 92 special cases such as initial time steps vs. subsequent iterations, 93 ensuring dendrites receive the appropriate inputs (e.g., external inputs 94 vs. internal recurrent state). 95 96 Parameters 97 ---------- 98 *args : tuple 99 Positional arguments containing inputs to the PAI module. 100 **kwargs : dict 101 Keyword arguments containing inputs to the PAI module. 102 103 Returns 104 ------- 105 tuple 106 A tuple of (processed_args, processed_kwargs) to pass to dendrite. 107 """ 108 pass 109 110 @abstractmethod 111 def post_d(self, *args, **kwargs): 112 """ 113 Post-process dendrite output and manage state. 114 115 Processes the output from dendrite operations, storing any state needed 116 for future iterations and returning only the portion that should be 117 combined with the neuron output. E.g. this is where recurrent state is 118 saved for the next time step. 119 120 Parameters 121 ---------- 122 *args : tuple 123 Positional arguments containing the dendrite output. 124 **kwargs : dict 125 Keyword arguments from the dendrite output. 126 127 Returns 128 ------- 129 Any 130 The filtered dendrite output to be added to the neuron output. 131 """ 132 pass 133 134 @abstractmethod 135 def clear_processor(self): 136 """ 137 Clear all internal processor state. 138 139 Resets the processor by removing all stored state variables. Must 140 be called before saving or safe_tensors will run into errors. 141 Implementations should safely check for attribute existence before 142 deletion to avoid errors. 143 144 Parameters 145 ---------- 146 None 147 148 Returns 149 ------- 150 None 151 This function does not return a value. 152 """ 153 pass 154 155 156# General multi output processor for any number that ignores later ones 157class MultiOutputProcessor: 158 """Processor for handling multiple outputs, ignoring later ones.""" 159 160 def post_n1(self, *args, **kwargs): 161 """Saves extra outputs and returns the first output. 162 163 Parameters 164 ---------- 165 *args : tuple 166 Contains the modules output tuple. 167 **kwargs : dict 168 Unused keyword arguments. 169 170 Returns 171 ------- 172 torch.Tensor 173 The first tensor of the tuple 174 """ 175 out = args[0][0] 176 extra_out = args[0][1:] 177 self.extra_out = extra_out 178 return out 179 180 def post_n2(self, *args, **kwargs): 181 """Combine output with stored extra outputs. 182 183 Parameters 184 ---------- 185 *args : torch.tensor 186 The first tensor combined with dendrite output. 187 **kwargs : dict 188 Unused keyword arguments. 189 190 Returns 191 ------- 192 tuple 193 The recombined output tuple wth the new first output modified 194 """ 195 out = args[0] 196 if isinstance(self.extra_out, tuple): 197 return (out,) + self.extra_out 198 else: 199 return (out,) + (self.extra_out,) 200 201 def pre_d(self, *args, **kwargs): 202 """Pass through arguments unchanged for dendrite preprocessing. 203 204 Parameters 205 ---------- 206 *args : tuple 207 Positional arguments containing inputs to the PAI module. 208 **kwargs : dict 209 Keyword arguments containing inputs to the PAI module. 210 211 Returns 212 ------- 213 args : tuple 214 Positional arguments containing inputs to the PAI module. 215 kwargs : dict 216 Keyword arguments containing inputs to the PAI module. 217 """ 218 return args, kwargs 219 220 def post_d(self, *args, **kwargs): 221 """Extract first output for dendrite postprocessing. 222 223 Parameters 224 ---------- 225 *args : tuple 226 Contains the dendrite modules output tuple. 227 **kwargs : dict 228 Unused keyword arguments. 229 230 Returns 231 ------- 232 torch.Tensor 233 The first tensor of the tuple 234 """ 235 out = args[0][0] 236 return out 237 238 def clear_processor(self): 239 """Clear stored processor state. 240 241 Parameters 242 ---------- 243 None 244 245 Returns 246 ------- 247 None 248 This function does not return a value. 249 """ 250 251 if hasattr(self, "extra_out"): 252 delattr(self, "extra_out") 253 254class LSTMCellProcessor(PAIProcessor): 255 """Processor for LSTM cells to handle hidden and cell states.""" 256 257 def post_n1(self, *args, **kwargs): 258 """ 259 Extract hidden state from LSTM output for dendrite processing. 260 261 Separates the hidden state (h_t) from the cell state (c_t) in the 262 LSTM output tuple. Stores the cell state temporarily since only the 263 hidden state should be modified by dendrites. 264 265 Parameters 266 ---------- 267 *args : tuple 268 Contains LSTM output tuple (h_t, c_t) as first element. 269 **kwargs : dict 270 Unused keyword arguments. 271 272 Returns 273 ------- 274 torch.Tensor 275 Hidden state h_t to be passed to dendrite processing. 276 """ 277 h_t = args[0][0] 278 c_t = args[0][1] 279 # Store the cell state temporarily and just use the hidden state 280 # to do Dendrite functions 281 self.c_t_n = c_t 282 return h_t 283 284 def post_n2(self, *args, **kwargs): 285 """ 286 Recombine dendrite-modified hidden state with cell state. 287 288 Takes the hidden state that has been modified by dendrite operations 289 and combines it with the stored cell state to produce the complete 290 LSTM output tuple. 291 292 Parameters 293 ---------- 294 *args : tuple 295 Contains the dendrite-modified hidden state h_t. 296 **kwargs : dict 297 Unused keyword arguments. 298 299 Returns 300 ------- 301 tuple 302 Complete LSTM output (h_t, c_t) where h_t has been modified. 303 """ 304 h_t = args[0] 305 return h_t, self.c_t_n 306 307 def pre_d(self, *args, **kwargs): 308 """ 309 Filter LSTMCell input for dendrite based on initialization state. 310 311 Checks if this is the first time step (all zeros in h_t) or a 312 subsequent step. For the first step, passes through the original 313 inputs. For subsequent steps, replaces the neuron's hidden state 314 with the dendrite's own internal state from the previous iteration. 315 316 Parameters 317 ---------- 318 *args : tuple 319 Contains (input, (h_t, c_t)) where input is the external input 320 and (h_t, c_t) is the neuron's recurrent state. 321 **kwargs : dict 322 Keyword arguments to pass through. 323 324 Returns 325 ------- 326 tuple 327 ((processed_input, processed_state), kwargs) for dendrite call. 328 """ 329 h_t = args[1][0] 330 # If its the initial step then just use the normal input and zeros 331 if h_t.sum() == 0: 332 return args, kwargs 333 # If its not the first one then return the input it got with its own 334 # h_t and c_t to replace neurons 335 else: 336 return (args[0], (self.h_t_d, self.c_t_d)), kwargs 337 338 def post_d(self, *args, **kwargs): 339 """ 340 Extract and store dendrite's LSTM state for next iteration. 341 342 Separates the dendrite's hidden and cell states from its output tuple, 343 stores both for use in the next time step, and returns only the hidden 344 state to be combined with the neuron's output. 345 346 Parameters 347 ---------- 348 *args : tuple 349 Contains dendrite LSTM output tuple (h_t, c_t). 350 **kwargs : dict 351 Unused keyword arguments. 352 353 Returns 354 ------- 355 torch.Tensor 356 Hidden state h_t to be added to the neuron output. 357 """ 358 h_t = args[0][0] 359 c_t = args[0][1] 360 self.h_t_d = h_t 361 self.c_t_d = c_t 362 return h_t 363 364 def clear_processor(self): 365 """ 366 Clear all stored LSTM states. 367 368 Removes dendrite hidden state (h_t_d), dendrite cell state (c_t_d), 369 and temporarily stored neuron cell state (c_t_n). Safe to call even 370 if attributes don't exist. 371 372 Parameters 373 ---------- 374 None 375 376 Returns 377 ------- 378 None 379 This function does not return a value. 380 """ 381 if hasattr(self, "h_t_d"): 382 delattr(self, "h_t_d") 383 if hasattr(self, "c_t_d"): 384 delattr(self, "c_t_d") 385 if hasattr(self, "c_t_n"): 386 delattr(self, "c_t_n") 387 388 389 390class LSTMProcessor(PAIProcessor): 391 """Processor for LSTM to handle hidden and output states.""" 392 393 def post_n1(self, *args, **kwargs): 394 """ 395 Extract hidden state from LSTM output for dendrite processing. 396 397 Separates the hidden state from the output in the 398 LSTM output tuple. Stores the hidden state temporarily since only the 399 output state should be modified by dendrites. 400 401 Parameters 402 ---------- 403 *args : tuple 404 Contains LSTM output tuple (output, hidden) as first element. 405 **kwargs : dict 406 Unused keyword arguments. 407 408 Returns 409 ------- 410 torch.Tensor 411 Output state to be passed to dendrite processing. 412 """ 413 output = args[0][0] 414 hidden = args[0][1] 415 # Store the hidden state temporarily and just use the output state 416 # to do Dendrite functions 417 self.hidden_n = hidden 418 return output 419 420 def post_n2(self, *args, **kwargs): 421 """ 422 Recombine dendrite-modified output with hidden tuple. 423 424 Takes the output state that has been modified by dendrite operations 425 and combines it with the stored hidden state to produce the complete 426 LSTM output tuple. 427 428 Parameters 429 ---------- 430 *args : tuple 431 Contains the dendrite-modified output state. 432 **kwargs : dict 433 Unused keyword arguments. 434 435 Returns 436 ------- 437 tuple 438 Complete LSTM output (output, hidden) where output has been modified. 439 """ 440 output = args[0] 441 return output, self.hidden_n 442 443 def pre_d(self, *args, **kwargs): 444 """ 445 LSTM input is just the tensor which also goes to the dendrite 446 447 Parameters 448 ---------- 449 *args : 450 Input tensor 451 **kwargs : dict 452 Empty 453 454 Returns 455 ------- 456 tuple 457 (output, hidden) 458 """ 459 return args, kwargs 460 461 def post_d(self, *args, **kwargs): 462 """ 463 Extract dendrite's output to combine. 464 465 Parameters 466 ---------- 467 *args : tuple 468 Contains dendrite LSTM output tuple (output, hidden). 469 **kwargs : dict 470 Unused keyword arguments. 471 472 Returns 473 ------- 474 torch.Tensor 475 Output state to be added to the neuron output. 476 """ 477 output = args[0][0] 478 hidden = args[0][1] 479 return output 480 481 def clear_processor(self): 482 """ 483 Clear all stored LSTM states. 484 485 486 Parameters 487 ---------- 488 None 489 490 Returns 491 ------- 492 None 493 This function does not return a value. 494 """ 495 if hasattr(self, "hidden_n"): 496 delattr(self, "hidden_n") 497 498 499class LSTMProcessorLastHidden(PAIProcessor): 500 """Processor for LSTM to forward the last hidden.""" 501 502 def post_n1(self, *args, **kwargs): 503 """ 504 Extract the last hidden to combine with dendrites 505 506 Parameters 507 ---------- 508 *args : tuple 509 Contains LSTM output tuple (output, hidden) as first element. 510 **kwargs : dict 511 Unused keyword arguments. 512 513 Returns 514 ------- 515 torch.Tensor 516 Output state to be passed to dendrite processing. 517 """ 518 ignored_output = args[0][0] 519 last_hidden = args[0][1][-1] 520 521 return last_hidden 522 523 def post_n2(self, *args, **kwargs): 524 """ 525 Recombine dendrite-modified last hidden, and append None just to maintain output format 526 527 Parameters 528 ---------- 529 *args : tuple 530 Contains the dendrite-modified output state. 531 **kwargs : dict 532 Unused keyword arguments. 533 534 Returns 535 ------- 536 tuple 537 Complete LSTM output (output, hidden) where output has been modified. 538 """ 539 combined_last_hidden = args[0] 540 return None, combined_last_hidden 541 542 def pre_d(self, *args, **kwargs): 543 """ 544 LSTM input is just the tensor which also goes to the dendrite 545 546 Parameters 547 ---------- 548 *args : 549 Input tensor 550 **kwargs : dict 551 Empty 552 553 Returns 554 ------- 555 tuple 556 (output, hidden) 557 """ 558 return args, kwargs 559 560 def post_d(self, *args, **kwargs): 561 """ 562 Extract extract the dendrites last hidden to combine with neurons. 563 564 Parameters 565 ---------- 566 *args : tuple 567 Contains dendrite LSTM output tuple (output, hidden). 568 **kwargs : dict 569 Unused keyword arguments. 570 571 Returns 572 ------- 573 torch.Tensor 574 Output state to be added to the neuron output. 575 """ 576 ignored_output = args[0][0] 577 last_hidden = args[0][1][-1] 578 return last_hidden 579 580 def clear_processor(self): 581 """Clear processor state. 582 583 Notes 584 ----- 585 This processor keeps no persistent internal state. 586 587 Parameters 588 ---------- 589 None 590 591 Returns 592 ------- 593 None 594 This function does not return a value. 595 """ 596 # Nothing is stored 597 pass 598 599class ResNetPAI(nn.Module): 600 """PB-compatible ResNet wrapper. 601 602 All normalization layers should be wrapped in a PAISequential, or other 603 wrapped module. When working with a predefined model the following shows 604 an example of how to create a module for modules_to_replace. 605 """ 606 607 def __init__(self, other_resnet): 608 """Initialize ResNetPAI from existing ResNet model. 609 610 Parameters 611 ---------- 612 *args : other_resnet : torchvision.models.resnet.ResNet 613 An existing ResNet model to convert to PAI-compatible format. 614 """ 615 super(ResNetPAI, self).__init__() 616 617 # For the most part, just copy the exact values from the original module 618 self._norm_layer = other_resnet._norm_layer 619 self.inplanes = other_resnet.inplanes 620 self.dilation = other_resnet.dilation 621 self.groups = other_resnet.groups 622 self.base_width = other_resnet.base_width 623 624 # For the component to be changed, define a PAISequential with the old 625 # modules included 626 self.b1 = GPA.PAISequential([other_resnet.conv1, other_resnet.bn1]) 627 628 self.relu = other_resnet.relu 629 self.maxpool = other_resnet.maxpool 630 631 for i in range(1, 5): 632 layer_name = "layer" + str(i) 633 original_layer = getattr(other_resnet, layer_name) 634 pb_layer = self._make_layer_pb(original_layer, other_resnet, i) 635 setattr(self, layer_name, pb_layer) 636 637 self.avgpool = other_resnet.avgpool 638 self.fc = other_resnet.fc 639 640 def _make_layer_pb(self, other_block_set, other_resnet, block_id): 641 """Convert ResNet layer blocks to PB-compatible format. 642 643 Parameters 644 ---------- 645 other_block_set : torch.vision.models.resnet.any_block 646 A set of blocks from the original ResNet model. 647 other_resnet : torchvision.models.resnet.ResNet 648 The original ResNet model. 649 block_id : int 650 The layer number being converted. 651 Returns 652 ------- 653 nn.Sequential 654 A sequential container with the converted blocks. 655 """ 656 layers = [] 657 for i in range(len(other_block_set)): 658 block_type = type(other_block_set[i]) 659 if block_type == resnet_pt.BasicBlock: 660 layers.append(other_block_set[i]) 661 elif block_type == resnet_pt.Bottleneck: 662 layers.append(other_block_set[i]) 663 else: 664 print( 665 "Your resnet uses a block type that has not been " 666 "accounted for. Customization might be required." 667 ) 668 layer_name = "layer" + str(block_id) 669 print(type(getattr(other_resnet, layer_name))) 670 pdb.set_trace() 671 return nn.Sequential(*layers) 672 673 def _forward_impl(self, x): 674 """Implementation of the forward pass. 675 676 Parameters 677 ---------- 678 x : torch.Tensor 679 Input tensor to the network. 680 681 Returns 682 ------- 683 torch.Tensor 684 Output tensor from the network. 685 """ 686 # Modified b1 rather than conv1 and bn1 687 x = self.b1(x) 688 # Rest of forward remains the same 689 x = F.relu(x) 690 x = self.maxpool(x) 691 692 x = self.layer1(x) 693 x = self.layer2(x) 694 x = self.layer3(x) 695 x = self.layer4(x) 696 697 x = self.avgpool(x) 698 x = torch.flatten(x, 1) 699 x = self.fc(x) 700 701 return x 702 703 def forward(self, x): 704 """Forward pass through the network. 705 706 Parameters 707 ---------- 708 x : torch.Tensor 709 Input tensor to the network. 710 711 Returns 712 ------- 713 torch.Tensor 714 Output tensor from the network. 715 """ 716 return self._forward_impl(x) 717 718 719class ResNetPAIPreFC(nn.Module): 720 """PB-compatible ResNet wrapper. 721 722 All normalization layers should be wrapped in a PAISequential, or other 723 wrapped module. When working with a predefined model the following shows 724 an example of how to create a module for modules_to_replace. 725 """ 726 727 def __init__(self, other_resnet): 728 """Initialize ResNetPAI from existing ResNet model. 729 730 Parameters 731 ---------- 732 *args : other_resnet : torchvision.models.resnet.ResNet 733 An existing ResNet model to convert to PAI-compatible format. 734 """ 735 super(ResNetPAIPreFC, self).__init__() 736 737 # For the most part, just copy the exact values from the original module 738 self._norm_layer = other_resnet._norm_layer 739 self.inplanes = other_resnet.inplanes 740 self.dilation = other_resnet.dilation 741 self.groups = other_resnet.groups 742 self.base_width = other_resnet.base_width 743 744 # For the component to be changed, define a PAISequential with the old 745 # modules included 746 self.conv1 = other_resnet.conv1 747 self.bn1 = other_resnet.bn1 748 749 self.relu = other_resnet.relu 750 self.maxpool = other_resnet.maxpool 751 752 for i in range(1, 5): 753 layer_name = "layer" + str(i) 754 original_layer = getattr(other_resnet, layer_name) 755 pb_layer = self._make_layer_pb(original_layer, other_resnet, i) 756 setattr(self, layer_name, pb_layer) 757 758 self.avgpool = other_resnet.avgpool 759 760 # Create pre_fc layer with dimensions matching layer4 output (same as fc input) 761 fc_in_features = other_resnet.fc.in_features 762 self.pre_fc = nn.Linear(fc_in_features, fc_in_features) 763 764 self.fc = other_resnet.fc 765 766 def _make_layer_pb(self, other_block_set, other_resnet, block_id): 767 """Convert ResNet layer blocks to PB-compatible format. 768 769 Parameters 770 ---------- 771 other_block_set : torch.vision.models.resnet.any_block 772 A set of blocks from the original ResNet model. 773 other_resnet : torchvision.models.resnet.ResNet 774 The original ResNet model. 775 block_id : int 776 The layer number being converted. 777 Returns 778 ------- 779 nn.Sequential 780 A sequential container with the converted blocks. 781 """ 782 layers = [] 783 for i in range(len(other_block_set)): 784 block_type = type(other_block_set[i]) 785 if block_type == resnet_pt.BasicBlock: 786 layers.append(other_block_set[i]) 787 elif block_type == resnet_pt.Bottleneck: 788 layers.append(other_block_set[i]) 789 else: 790 print( 791 "Your resnet uses a block type that has not been " 792 "accounted for. Customization might be required." 793 ) 794 layer_name = "layer" + str(block_id) 795 print(type(getattr(other_resnet, layer_name))) 796 pdb.set_trace() 797 return nn.Sequential(*layers) 798 799 def _forward_impl(self, x): 800 """Implementation of the forward pass. 801 802 Parameters 803 ---------- 804 x : torch.Tensor 805 Input tensor to the network. 806 807 Returns 808 ------- 809 torch.Tensor 810 Output tensor from the network. 811 """ 812 # Modified b1 rather than conv1 and bn1 813 x = self.conv1(x) 814 x = self.bn1(x) 815 # Rest of forward remains the same 816 x = F.relu(x) 817 x = self.maxpool(x) 818 819 x = self.layer1(x) 820 x = self.layer2(x) 821 x = self.layer3(x) 822 x = self.layer4(x) 823 824 x = self.avgpool(x) 825 x = torch.flatten(x, 1) 826 x = self.pre_fc(x) 827 x = F.relu(x) 828 x = self.fc(x) 829 830 return x 831 832 def forward(self, x): 833 """Forward pass through the network. 834 835 Parameters 836 ---------- 837 x : torch.Tensor 838 Input tensor to the network. 839 840 Returns 841 ------- 842 torch.Tensor 843 Output tensor from the network. 844 """ 845 return self._forward_impl(x)
28class PAIProcessor(ABC): 29 """ 30 Abstract base class for processing neuron and dendrite operations. 31 32 Processors handle state management and data flow between neurons and 33 dendrites, allowing for custom pre/post processing of modules which have 34 multiple inputs and outputs, rather than the default single tensor input/output. 35 Subclasses should implement the five core processing methods to handle 36 their specific state management needs. 37 """ 38 39 @abstractmethod 40 def post_n1(self, *args, **kwargs): 41 """ 42 Post-process neuron output before dendrite processing. 43 44 Called immediately after the main module/neuron is executed and before 45 any dendrite processing occurs. This method should extract and return 46 only the tensor of the neuron output that should be seen by 47 dendrite operations. 48 49 Parameters 50 ---------- 51 *args : tuple 52 Positional arguments, typically containing the neuron output. 53 **kwargs : dict 54 Keyword arguments from the neuron output. 55 56 Returns 57 ------- 58 Any 59 The filtered output to be passed to dendrite processing. 60 """ 61 pass 62 63 @abstractmethod 64 def post_n2(self, *args, **kwargs): 65 """ 66 Post-process dendrite-modified output before final return. 67 68 Called after dendrite processing is complete and before passing the 69 final value forward in the network. This method should combine the 70 dendrite-modified output with any stored state to produce the complete 71 output that matches the expected format of the main module. 72 73 Parameters 74 ---------- 75 *args : tuple 76 Positional arguments containing the dendrite-modified output. 77 **kwargs : dict 78 Keyword arguments from the processing chain. 79 80 Returns 81 ------- 82 Any 83 The complete output in the format expected by downstream components. 84 """ 85 pass 86 87 @abstractmethod 88 def pre_d(self, *args, **kwargs): 89 """ 90 Pre-process input before dendrite operations. 91 92 Filters and prepares inputs for dendrite processing. This method handles 93 special cases such as initial time steps vs. subsequent iterations, 94 ensuring dendrites receive the appropriate inputs (e.g., external inputs 95 vs. internal recurrent state). 96 97 Parameters 98 ---------- 99 *args : tuple 100 Positional arguments containing inputs to the PAI module. 101 **kwargs : dict 102 Keyword arguments containing inputs to the PAI module. 103 104 Returns 105 ------- 106 tuple 107 A tuple of (processed_args, processed_kwargs) to pass to dendrite. 108 """ 109 pass 110 111 @abstractmethod 112 def post_d(self, *args, **kwargs): 113 """ 114 Post-process dendrite output and manage state. 115 116 Processes the output from dendrite operations, storing any state needed 117 for future iterations and returning only the portion that should be 118 combined with the neuron output. E.g. this is where recurrent state is 119 saved for the next time step. 120 121 Parameters 122 ---------- 123 *args : tuple 124 Positional arguments containing the dendrite output. 125 **kwargs : dict 126 Keyword arguments from the dendrite output. 127 128 Returns 129 ------- 130 Any 131 The filtered dendrite output to be added to the neuron output. 132 """ 133 pass 134 135 @abstractmethod 136 def clear_processor(self): 137 """ 138 Clear all internal processor state. 139 140 Resets the processor by removing all stored state variables. Must 141 be called before saving or safe_tensors will run into errors. 142 Implementations should safely check for attribute existence before 143 deletion to avoid errors. 144 145 Parameters 146 ---------- 147 None 148 149 Returns 150 ------- 151 None 152 This function does not return a value. 153 """ 154 pass
Abstract base class for processing neuron and dendrite operations.
Processors handle state management and data flow between neurons and dendrites, allowing for custom pre/post processing of modules which have multiple inputs and outputs, rather than the default single tensor input/output. Subclasses should implement the five core processing methods to handle their specific state management needs.
39 @abstractmethod 40 def post_n1(self, *args, **kwargs): 41 """ 42 Post-process neuron output before dendrite processing. 43 44 Called immediately after the main module/neuron is executed and before 45 any dendrite processing occurs. This method should extract and return 46 only the tensor of the neuron output that should be seen by 47 dendrite operations. 48 49 Parameters 50 ---------- 51 *args : tuple 52 Positional arguments, typically containing the neuron output. 53 **kwargs : dict 54 Keyword arguments from the neuron output. 55 56 Returns 57 ------- 58 Any 59 The filtered output to be passed to dendrite processing. 60 """ 61 pass
Post-process neuron output before dendrite processing.
Called immediately after the main module/neuron is executed and before any dendrite processing occurs. This method should extract and return only the tensor of the neuron output that should be seen by dendrite operations.
Parameters
- *args (tuple): Positional arguments, typically containing the neuron output.
- **kwargs (dict): Keyword arguments from the neuron output.
Returns
- Any: The filtered output to be passed to dendrite processing.
63 @abstractmethod 64 def post_n2(self, *args, **kwargs): 65 """ 66 Post-process dendrite-modified output before final return. 67 68 Called after dendrite processing is complete and before passing the 69 final value forward in the network. This method should combine the 70 dendrite-modified output with any stored state to produce the complete 71 output that matches the expected format of the main module. 72 73 Parameters 74 ---------- 75 *args : tuple 76 Positional arguments containing the dendrite-modified output. 77 **kwargs : dict 78 Keyword arguments from the processing chain. 79 80 Returns 81 ------- 82 Any 83 The complete output in the format expected by downstream components. 84 """ 85 pass
Post-process dendrite-modified output before final return.
Called after dendrite processing is complete and before passing the final value forward in the network. This method should combine the dendrite-modified output with any stored state to produce the complete output that matches the expected format of the main module.
Parameters
- *args (tuple): Positional arguments containing the dendrite-modified output.
- **kwargs (dict): Keyword arguments from the processing chain.
Returns
- Any: The complete output in the format expected by downstream components.
87 @abstractmethod 88 def pre_d(self, *args, **kwargs): 89 """ 90 Pre-process input before dendrite operations. 91 92 Filters and prepares inputs for dendrite processing. This method handles 93 special cases such as initial time steps vs. subsequent iterations, 94 ensuring dendrites receive the appropriate inputs (e.g., external inputs 95 vs. internal recurrent state). 96 97 Parameters 98 ---------- 99 *args : tuple 100 Positional arguments containing inputs to the PAI module. 101 **kwargs : dict 102 Keyword arguments containing inputs to the PAI module. 103 104 Returns 105 ------- 106 tuple 107 A tuple of (processed_args, processed_kwargs) to pass to dendrite. 108 """ 109 pass
Pre-process input before dendrite operations.
Filters and prepares inputs for dendrite processing. This method handles special cases such as initial time steps vs. subsequent iterations, ensuring dendrites receive the appropriate inputs (e.g., external inputs vs. internal recurrent state).
Parameters
- *args (tuple): Positional arguments containing inputs to the PAI module.
- **kwargs (dict): Keyword arguments containing inputs to the PAI module.
Returns
- tuple: A tuple of (processed_args, processed_kwargs) to pass to dendrite.
111 @abstractmethod 112 def post_d(self, *args, **kwargs): 113 """ 114 Post-process dendrite output and manage state. 115 116 Processes the output from dendrite operations, storing any state needed 117 for future iterations and returning only the portion that should be 118 combined with the neuron output. E.g. this is where recurrent state is 119 saved for the next time step. 120 121 Parameters 122 ---------- 123 *args : tuple 124 Positional arguments containing the dendrite output. 125 **kwargs : dict 126 Keyword arguments from the dendrite output. 127 128 Returns 129 ------- 130 Any 131 The filtered dendrite output to be added to the neuron output. 132 """ 133 pass
Post-process dendrite output and manage state.
Processes the output from dendrite operations, storing any state needed for future iterations and returning only the portion that should be combined with the neuron output. E.g. this is where recurrent state is saved for the next time step.
Parameters
- *args (tuple): Positional arguments containing the dendrite output.
- **kwargs (dict): Keyword arguments from the dendrite output.
Returns
- Any: The filtered dendrite output to be added to the neuron output.
135 @abstractmethod 136 def clear_processor(self): 137 """ 138 Clear all internal processor state. 139 140 Resets the processor by removing all stored state variables. Must 141 be called before saving or safe_tensors will run into errors. 142 Implementations should safely check for attribute existence before 143 deletion to avoid errors. 144 145 Parameters 146 ---------- 147 None 148 149 Returns 150 ------- 151 None 152 This function does not return a value. 153 """ 154 pass
Clear all internal processor state.
Resets the processor by removing all stored state variables. Must be called before saving or safe_tensors will run into errors. Implementations should safely check for attribute existence before deletion to avoid errors.
Parameters
- None
Returns
- None: This function does not return a value.
158class MultiOutputProcessor: 159 """Processor for handling multiple outputs, ignoring later ones.""" 160 161 def post_n1(self, *args, **kwargs): 162 """Saves extra outputs and returns the first output. 163 164 Parameters 165 ---------- 166 *args : tuple 167 Contains the modules output tuple. 168 **kwargs : dict 169 Unused keyword arguments. 170 171 Returns 172 ------- 173 torch.Tensor 174 The first tensor of the tuple 175 """ 176 out = args[0][0] 177 extra_out = args[0][1:] 178 self.extra_out = extra_out 179 return out 180 181 def post_n2(self, *args, **kwargs): 182 """Combine output with stored extra outputs. 183 184 Parameters 185 ---------- 186 *args : torch.tensor 187 The first tensor combined with dendrite output. 188 **kwargs : dict 189 Unused keyword arguments. 190 191 Returns 192 ------- 193 tuple 194 The recombined output tuple wth the new first output modified 195 """ 196 out = args[0] 197 if isinstance(self.extra_out, tuple): 198 return (out,) + self.extra_out 199 else: 200 return (out,) + (self.extra_out,) 201 202 def pre_d(self, *args, **kwargs): 203 """Pass through arguments unchanged for dendrite preprocessing. 204 205 Parameters 206 ---------- 207 *args : tuple 208 Positional arguments containing inputs to the PAI module. 209 **kwargs : dict 210 Keyword arguments containing inputs to the PAI module. 211 212 Returns 213 ------- 214 args : tuple 215 Positional arguments containing inputs to the PAI module. 216 kwargs : dict 217 Keyword arguments containing inputs to the PAI module. 218 """ 219 return args, kwargs 220 221 def post_d(self, *args, **kwargs): 222 """Extract first output for dendrite postprocessing. 223 224 Parameters 225 ---------- 226 *args : tuple 227 Contains the dendrite modules output tuple. 228 **kwargs : dict 229 Unused keyword arguments. 230 231 Returns 232 ------- 233 torch.Tensor 234 The first tensor of the tuple 235 """ 236 out = args[0][0] 237 return out 238 239 def clear_processor(self): 240 """Clear stored processor state. 241 242 Parameters 243 ---------- 244 None 245 246 Returns 247 ------- 248 None 249 This function does not return a value. 250 """ 251 252 if hasattr(self, "extra_out"): 253 delattr(self, "extra_out")
Processor for handling multiple outputs, ignoring later ones.
161 def post_n1(self, *args, **kwargs): 162 """Saves extra outputs and returns the first output. 163 164 Parameters 165 ---------- 166 *args : tuple 167 Contains the modules output tuple. 168 **kwargs : dict 169 Unused keyword arguments. 170 171 Returns 172 ------- 173 torch.Tensor 174 The first tensor of the tuple 175 """ 176 out = args[0][0] 177 extra_out = args[0][1:] 178 self.extra_out = extra_out 179 return out
Saves extra outputs and returns the first output.
Parameters
- *args (tuple): Contains the modules output tuple.
- **kwargs (dict): Unused keyword arguments.
Returns
- torch.Tensor: The first tensor of the tuple
181 def post_n2(self, *args, **kwargs): 182 """Combine output with stored extra outputs. 183 184 Parameters 185 ---------- 186 *args : torch.tensor 187 The first tensor combined with dendrite output. 188 **kwargs : dict 189 Unused keyword arguments. 190 191 Returns 192 ------- 193 tuple 194 The recombined output tuple wth the new first output modified 195 """ 196 out = args[0] 197 if isinstance(self.extra_out, tuple): 198 return (out,) + self.extra_out 199 else: 200 return (out,) + (self.extra_out,)
Combine output with stored extra outputs.
Parameters
- *args (torch.tensor): The first tensor combined with dendrite output.
- **kwargs (dict): Unused keyword arguments.
Returns
- tuple: The recombined output tuple wth the new first output modified
202 def pre_d(self, *args, **kwargs): 203 """Pass through arguments unchanged for dendrite preprocessing. 204 205 Parameters 206 ---------- 207 *args : tuple 208 Positional arguments containing inputs to the PAI module. 209 **kwargs : dict 210 Keyword arguments containing inputs to the PAI module. 211 212 Returns 213 ------- 214 args : tuple 215 Positional arguments containing inputs to the PAI module. 216 kwargs : dict 217 Keyword arguments containing inputs to the PAI module. 218 """ 219 return args, kwargs
Pass through arguments unchanged for dendrite preprocessing.
Parameters
- *args (tuple): Positional arguments containing inputs to the PAI module.
- **kwargs (dict): Keyword arguments containing inputs to the PAI module.
Returns
- args (tuple): Positional arguments containing inputs to the PAI module.
- kwargs (dict): Keyword arguments containing inputs to the PAI module.
221 def post_d(self, *args, **kwargs): 222 """Extract first output for dendrite postprocessing. 223 224 Parameters 225 ---------- 226 *args : tuple 227 Contains the dendrite modules output tuple. 228 **kwargs : dict 229 Unused keyword arguments. 230 231 Returns 232 ------- 233 torch.Tensor 234 The first tensor of the tuple 235 """ 236 out = args[0][0] 237 return out
Extract first output for dendrite postprocessing.
Parameters
- *args (tuple): Contains the dendrite modules output tuple.
- **kwargs (dict): Unused keyword arguments.
Returns
- torch.Tensor: The first tensor of the tuple
239 def clear_processor(self): 240 """Clear stored processor state. 241 242 Parameters 243 ---------- 244 None 245 246 Returns 247 ------- 248 None 249 This function does not return a value. 250 """ 251 252 if hasattr(self, "extra_out"): 253 delattr(self, "extra_out")
Clear stored processor state.
Parameters
- None
Returns
- None: This function does not return a value.
255class LSTMCellProcessor(PAIProcessor): 256 """Processor for LSTM cells to handle hidden and cell states.""" 257 258 def post_n1(self, *args, **kwargs): 259 """ 260 Extract hidden state from LSTM output for dendrite processing. 261 262 Separates the hidden state (h_t) from the cell state (c_t) in the 263 LSTM output tuple. Stores the cell state temporarily since only the 264 hidden state should be modified by dendrites. 265 266 Parameters 267 ---------- 268 *args : tuple 269 Contains LSTM output tuple (h_t, c_t) as first element. 270 **kwargs : dict 271 Unused keyword arguments. 272 273 Returns 274 ------- 275 torch.Tensor 276 Hidden state h_t to be passed to dendrite processing. 277 """ 278 h_t = args[0][0] 279 c_t = args[0][1] 280 # Store the cell state temporarily and just use the hidden state 281 # to do Dendrite functions 282 self.c_t_n = c_t 283 return h_t 284 285 def post_n2(self, *args, **kwargs): 286 """ 287 Recombine dendrite-modified hidden state with cell state. 288 289 Takes the hidden state that has been modified by dendrite operations 290 and combines it with the stored cell state to produce the complete 291 LSTM output tuple. 292 293 Parameters 294 ---------- 295 *args : tuple 296 Contains the dendrite-modified hidden state h_t. 297 **kwargs : dict 298 Unused keyword arguments. 299 300 Returns 301 ------- 302 tuple 303 Complete LSTM output (h_t, c_t) where h_t has been modified. 304 """ 305 h_t = args[0] 306 return h_t, self.c_t_n 307 308 def pre_d(self, *args, **kwargs): 309 """ 310 Filter LSTMCell input for dendrite based on initialization state. 311 312 Checks if this is the first time step (all zeros in h_t) or a 313 subsequent step. For the first step, passes through the original 314 inputs. For subsequent steps, replaces the neuron's hidden state 315 with the dendrite's own internal state from the previous iteration. 316 317 Parameters 318 ---------- 319 *args : tuple 320 Contains (input, (h_t, c_t)) where input is the external input 321 and (h_t, c_t) is the neuron's recurrent state. 322 **kwargs : dict 323 Keyword arguments to pass through. 324 325 Returns 326 ------- 327 tuple 328 ((processed_input, processed_state), kwargs) for dendrite call. 329 """ 330 h_t = args[1][0] 331 # If its the initial step then just use the normal input and zeros 332 if h_t.sum() == 0: 333 return args, kwargs 334 # If its not the first one then return the input it got with its own 335 # h_t and c_t to replace neurons 336 else: 337 return (args[0], (self.h_t_d, self.c_t_d)), kwargs 338 339 def post_d(self, *args, **kwargs): 340 """ 341 Extract and store dendrite's LSTM state for next iteration. 342 343 Separates the dendrite's hidden and cell states from its output tuple, 344 stores both for use in the next time step, and returns only the hidden 345 state to be combined with the neuron's output. 346 347 Parameters 348 ---------- 349 *args : tuple 350 Contains dendrite LSTM output tuple (h_t, c_t). 351 **kwargs : dict 352 Unused keyword arguments. 353 354 Returns 355 ------- 356 torch.Tensor 357 Hidden state h_t to be added to the neuron output. 358 """ 359 h_t = args[0][0] 360 c_t = args[0][1] 361 self.h_t_d = h_t 362 self.c_t_d = c_t 363 return h_t 364 365 def clear_processor(self): 366 """ 367 Clear all stored LSTM states. 368 369 Removes dendrite hidden state (h_t_d), dendrite cell state (c_t_d), 370 and temporarily stored neuron cell state (c_t_n). Safe to call even 371 if attributes don't exist. 372 373 Parameters 374 ---------- 375 None 376 377 Returns 378 ------- 379 None 380 This function does not return a value. 381 """ 382 if hasattr(self, "h_t_d"): 383 delattr(self, "h_t_d") 384 if hasattr(self, "c_t_d"): 385 delattr(self, "c_t_d") 386 if hasattr(self, "c_t_n"): 387 delattr(self, "c_t_n")
Processor for LSTM cells to handle hidden and cell states.
258 def post_n1(self, *args, **kwargs): 259 """ 260 Extract hidden state from LSTM output for dendrite processing. 261 262 Separates the hidden state (h_t) from the cell state (c_t) in the 263 LSTM output tuple. Stores the cell state temporarily since only the 264 hidden state should be modified by dendrites. 265 266 Parameters 267 ---------- 268 *args : tuple 269 Contains LSTM output tuple (h_t, c_t) as first element. 270 **kwargs : dict 271 Unused keyword arguments. 272 273 Returns 274 ------- 275 torch.Tensor 276 Hidden state h_t to be passed to dendrite processing. 277 """ 278 h_t = args[0][0] 279 c_t = args[0][1] 280 # Store the cell state temporarily and just use the hidden state 281 # to do Dendrite functions 282 self.c_t_n = c_t 283 return h_t
Extract hidden state from LSTM output for dendrite processing.
Separates the hidden state (h_t) from the cell state (c_t) in the LSTM output tuple. Stores the cell state temporarily since only the hidden state should be modified by dendrites.
Parameters
- *args (tuple): Contains LSTM output tuple (h_t, c_t) as first element.
- **kwargs (dict): Unused keyword arguments.
Returns
- torch.Tensor: Hidden state h_t to be passed to dendrite processing.
285 def post_n2(self, *args, **kwargs): 286 """ 287 Recombine dendrite-modified hidden state with cell state. 288 289 Takes the hidden state that has been modified by dendrite operations 290 and combines it with the stored cell state to produce the complete 291 LSTM output tuple. 292 293 Parameters 294 ---------- 295 *args : tuple 296 Contains the dendrite-modified hidden state h_t. 297 **kwargs : dict 298 Unused keyword arguments. 299 300 Returns 301 ------- 302 tuple 303 Complete LSTM output (h_t, c_t) where h_t has been modified. 304 """ 305 h_t = args[0] 306 return h_t, self.c_t_n
Recombine dendrite-modified hidden state with cell state.
Takes the hidden state that has been modified by dendrite operations and combines it with the stored cell state to produce the complete LSTM output tuple.
Parameters
- *args (tuple): Contains the dendrite-modified hidden state h_t.
- **kwargs (dict): Unused keyword arguments.
Returns
- tuple: Complete LSTM output (h_t, c_t) where h_t has been modified.
308 def pre_d(self, *args, **kwargs): 309 """ 310 Filter LSTMCell input for dendrite based on initialization state. 311 312 Checks if this is the first time step (all zeros in h_t) or a 313 subsequent step. For the first step, passes through the original 314 inputs. For subsequent steps, replaces the neuron's hidden state 315 with the dendrite's own internal state from the previous iteration. 316 317 Parameters 318 ---------- 319 *args : tuple 320 Contains (input, (h_t, c_t)) where input is the external input 321 and (h_t, c_t) is the neuron's recurrent state. 322 **kwargs : dict 323 Keyword arguments to pass through. 324 325 Returns 326 ------- 327 tuple 328 ((processed_input, processed_state), kwargs) for dendrite call. 329 """ 330 h_t = args[1][0] 331 # If its the initial step then just use the normal input and zeros 332 if h_t.sum() == 0: 333 return args, kwargs 334 # If its not the first one then return the input it got with its own 335 # h_t and c_t to replace neurons 336 else: 337 return (args[0], (self.h_t_d, self.c_t_d)), kwargs
Filter LSTMCell input for dendrite based on initialization state.
Checks if this is the first time step (all zeros in h_t) or a subsequent step. For the first step, passes through the original inputs. For subsequent steps, replaces the neuron's hidden state with the dendrite's own internal state from the previous iteration.
Parameters
- *args (tuple): Contains (input, (h_t, c_t)) where input is the external input and (h_t, c_t) is the neuron's recurrent state.
- **kwargs (dict): Keyword arguments to pass through.
Returns
- tuple: ((processed_input, processed_state), kwargs) for dendrite call.
339 def post_d(self, *args, **kwargs): 340 """ 341 Extract and store dendrite's LSTM state for next iteration. 342 343 Separates the dendrite's hidden and cell states from its output tuple, 344 stores both for use in the next time step, and returns only the hidden 345 state to be combined with the neuron's output. 346 347 Parameters 348 ---------- 349 *args : tuple 350 Contains dendrite LSTM output tuple (h_t, c_t). 351 **kwargs : dict 352 Unused keyword arguments. 353 354 Returns 355 ------- 356 torch.Tensor 357 Hidden state h_t to be added to the neuron output. 358 """ 359 h_t = args[0][0] 360 c_t = args[0][1] 361 self.h_t_d = h_t 362 self.c_t_d = c_t 363 return h_t
Extract and store dendrite's LSTM state for next iteration.
Separates the dendrite's hidden and cell states from its output tuple, stores both for use in the next time step, and returns only the hidden state to be combined with the neuron's output.
Parameters
- *args (tuple): Contains dendrite LSTM output tuple (h_t, c_t).
- **kwargs (dict): Unused keyword arguments.
Returns
- torch.Tensor: Hidden state h_t to be added to the neuron output.
365 def clear_processor(self): 366 """ 367 Clear all stored LSTM states. 368 369 Removes dendrite hidden state (h_t_d), dendrite cell state (c_t_d), 370 and temporarily stored neuron cell state (c_t_n). Safe to call even 371 if attributes don't exist. 372 373 Parameters 374 ---------- 375 None 376 377 Returns 378 ------- 379 None 380 This function does not return a value. 381 """ 382 if hasattr(self, "h_t_d"): 383 delattr(self, "h_t_d") 384 if hasattr(self, "c_t_d"): 385 delattr(self, "c_t_d") 386 if hasattr(self, "c_t_n"): 387 delattr(self, "c_t_n")
Clear all stored LSTM states.
Removes dendrite hidden state (h_t_d), dendrite cell state (c_t_d), and temporarily stored neuron cell state (c_t_n). Safe to call even if attributes don't exist.
Parameters
- None
Returns
- None: This function does not return a value.
391class LSTMProcessor(PAIProcessor): 392 """Processor for LSTM to handle hidden and output states.""" 393 394 def post_n1(self, *args, **kwargs): 395 """ 396 Extract hidden state from LSTM output for dendrite processing. 397 398 Separates the hidden state from the output in the 399 LSTM output tuple. Stores the hidden state temporarily since only the 400 output state should be modified by dendrites. 401 402 Parameters 403 ---------- 404 *args : tuple 405 Contains LSTM output tuple (output, hidden) as first element. 406 **kwargs : dict 407 Unused keyword arguments. 408 409 Returns 410 ------- 411 torch.Tensor 412 Output state to be passed to dendrite processing. 413 """ 414 output = args[0][0] 415 hidden = args[0][1] 416 # Store the hidden state temporarily and just use the output state 417 # to do Dendrite functions 418 self.hidden_n = hidden 419 return output 420 421 def post_n2(self, *args, **kwargs): 422 """ 423 Recombine dendrite-modified output with hidden tuple. 424 425 Takes the output state that has been modified by dendrite operations 426 and combines it with the stored hidden state to produce the complete 427 LSTM output tuple. 428 429 Parameters 430 ---------- 431 *args : tuple 432 Contains the dendrite-modified output state. 433 **kwargs : dict 434 Unused keyword arguments. 435 436 Returns 437 ------- 438 tuple 439 Complete LSTM output (output, hidden) where output has been modified. 440 """ 441 output = args[0] 442 return output, self.hidden_n 443 444 def pre_d(self, *args, **kwargs): 445 """ 446 LSTM input is just the tensor which also goes to the dendrite 447 448 Parameters 449 ---------- 450 *args : 451 Input tensor 452 **kwargs : dict 453 Empty 454 455 Returns 456 ------- 457 tuple 458 (output, hidden) 459 """ 460 return args, kwargs 461 462 def post_d(self, *args, **kwargs): 463 """ 464 Extract dendrite's output to combine. 465 466 Parameters 467 ---------- 468 *args : tuple 469 Contains dendrite LSTM output tuple (output, hidden). 470 **kwargs : dict 471 Unused keyword arguments. 472 473 Returns 474 ------- 475 torch.Tensor 476 Output state to be added to the neuron output. 477 """ 478 output = args[0][0] 479 hidden = args[0][1] 480 return output 481 482 def clear_processor(self): 483 """ 484 Clear all stored LSTM states. 485 486 487 Parameters 488 ---------- 489 None 490 491 Returns 492 ------- 493 None 494 This function does not return a value. 495 """ 496 if hasattr(self, "hidden_n"): 497 delattr(self, "hidden_n")
Processor for LSTM to handle hidden and output states.
394 def post_n1(self, *args, **kwargs): 395 """ 396 Extract hidden state from LSTM output for dendrite processing. 397 398 Separates the hidden state from the output in the 399 LSTM output tuple. Stores the hidden state temporarily since only the 400 output state should be modified by dendrites. 401 402 Parameters 403 ---------- 404 *args : tuple 405 Contains LSTM output tuple (output, hidden) as first element. 406 **kwargs : dict 407 Unused keyword arguments. 408 409 Returns 410 ------- 411 torch.Tensor 412 Output state to be passed to dendrite processing. 413 """ 414 output = args[0][0] 415 hidden = args[0][1] 416 # Store the hidden state temporarily and just use the output state 417 # to do Dendrite functions 418 self.hidden_n = hidden 419 return output
Extract hidden state from LSTM output for dendrite processing.
Separates the hidden state from the output in the LSTM output tuple. Stores the hidden state temporarily since only the output state should be modified by dendrites.
Parameters
- *args (tuple): Contains LSTM output tuple (output, hidden) as first element.
- **kwargs (dict): Unused keyword arguments.
Returns
- torch.Tensor: Output state to be passed to dendrite processing.
421 def post_n2(self, *args, **kwargs): 422 """ 423 Recombine dendrite-modified output with hidden tuple. 424 425 Takes the output state that has been modified by dendrite operations 426 and combines it with the stored hidden state to produce the complete 427 LSTM output tuple. 428 429 Parameters 430 ---------- 431 *args : tuple 432 Contains the dendrite-modified output state. 433 **kwargs : dict 434 Unused keyword arguments. 435 436 Returns 437 ------- 438 tuple 439 Complete LSTM output (output, hidden) where output has been modified. 440 """ 441 output = args[0] 442 return output, self.hidden_n
Recombine dendrite-modified output with hidden tuple.
Takes the output state that has been modified by dendrite operations and combines it with the stored hidden state to produce the complete LSTM output tuple.
Parameters
- *args (tuple): Contains the dendrite-modified output state.
- **kwargs (dict): Unused keyword arguments.
Returns
- tuple: Complete LSTM output (output, hidden) where output has been modified.
444 def pre_d(self, *args, **kwargs): 445 """ 446 LSTM input is just the tensor which also goes to the dendrite 447 448 Parameters 449 ---------- 450 *args : 451 Input tensor 452 **kwargs : dict 453 Empty 454 455 Returns 456 ------- 457 tuple 458 (output, hidden) 459 """ 460 return args, kwargs
LSTM input is just the tensor which also goes to the dendrite
Parameters
- *args (): Input tensor
- **kwargs (dict): Empty
Returns
- tuple: (output, hidden)
462 def post_d(self, *args, **kwargs): 463 """ 464 Extract dendrite's output to combine. 465 466 Parameters 467 ---------- 468 *args : tuple 469 Contains dendrite LSTM output tuple (output, hidden). 470 **kwargs : dict 471 Unused keyword arguments. 472 473 Returns 474 ------- 475 torch.Tensor 476 Output state to be added to the neuron output. 477 """ 478 output = args[0][0] 479 hidden = args[0][1] 480 return output
Extract dendrite's output to combine.
Parameters
- *args (tuple): Contains dendrite LSTM output tuple (output, hidden).
- **kwargs (dict): Unused keyword arguments.
Returns
- torch.Tensor: Output state to be added to the neuron output.
482 def clear_processor(self): 483 """ 484 Clear all stored LSTM states. 485 486 487 Parameters 488 ---------- 489 None 490 491 Returns 492 ------- 493 None 494 This function does not return a value. 495 """ 496 if hasattr(self, "hidden_n"): 497 delattr(self, "hidden_n")
Clear all stored LSTM states.
Parameters
- None
Returns
- None: This function does not return a value.
500class LSTMProcessorLastHidden(PAIProcessor): 501 """Processor for LSTM to forward the last hidden.""" 502 503 def post_n1(self, *args, **kwargs): 504 """ 505 Extract the last hidden to combine with dendrites 506 507 Parameters 508 ---------- 509 *args : tuple 510 Contains LSTM output tuple (output, hidden) as first element. 511 **kwargs : dict 512 Unused keyword arguments. 513 514 Returns 515 ------- 516 torch.Tensor 517 Output state to be passed to dendrite processing. 518 """ 519 ignored_output = args[0][0] 520 last_hidden = args[0][1][-1] 521 522 return last_hidden 523 524 def post_n2(self, *args, **kwargs): 525 """ 526 Recombine dendrite-modified last hidden, and append None just to maintain output format 527 528 Parameters 529 ---------- 530 *args : tuple 531 Contains the dendrite-modified output state. 532 **kwargs : dict 533 Unused keyword arguments. 534 535 Returns 536 ------- 537 tuple 538 Complete LSTM output (output, hidden) where output has been modified. 539 """ 540 combined_last_hidden = args[0] 541 return None, combined_last_hidden 542 543 def pre_d(self, *args, **kwargs): 544 """ 545 LSTM input is just the tensor which also goes to the dendrite 546 547 Parameters 548 ---------- 549 *args : 550 Input tensor 551 **kwargs : dict 552 Empty 553 554 Returns 555 ------- 556 tuple 557 (output, hidden) 558 """ 559 return args, kwargs 560 561 def post_d(self, *args, **kwargs): 562 """ 563 Extract extract the dendrites last hidden to combine with neurons. 564 565 Parameters 566 ---------- 567 *args : tuple 568 Contains dendrite LSTM output tuple (output, hidden). 569 **kwargs : dict 570 Unused keyword arguments. 571 572 Returns 573 ------- 574 torch.Tensor 575 Output state to be added to the neuron output. 576 """ 577 ignored_output = args[0][0] 578 last_hidden = args[0][1][-1] 579 return last_hidden 580 581 def clear_processor(self): 582 """Clear processor state. 583 584 Notes 585 ----- 586 This processor keeps no persistent internal state. 587 588 Parameters 589 ---------- 590 None 591 592 Returns 593 ------- 594 None 595 This function does not return a value. 596 """ 597 # Nothing is stored 598 pass
Processor for LSTM to forward the last hidden.
503 def post_n1(self, *args, **kwargs): 504 """ 505 Extract the last hidden to combine with dendrites 506 507 Parameters 508 ---------- 509 *args : tuple 510 Contains LSTM output tuple (output, hidden) as first element. 511 **kwargs : dict 512 Unused keyword arguments. 513 514 Returns 515 ------- 516 torch.Tensor 517 Output state to be passed to dendrite processing. 518 """ 519 ignored_output = args[0][0] 520 last_hidden = args[0][1][-1] 521 522 return last_hidden
Extract the last hidden to combine with dendrites
Parameters
- *args (tuple): Contains LSTM output tuple (output, hidden) as first element.
- **kwargs (dict): Unused keyword arguments.
Returns
- torch.Tensor: Output state to be passed to dendrite processing.
524 def post_n2(self, *args, **kwargs): 525 """ 526 Recombine dendrite-modified last hidden, and append None just to maintain output format 527 528 Parameters 529 ---------- 530 *args : tuple 531 Contains the dendrite-modified output state. 532 **kwargs : dict 533 Unused keyword arguments. 534 535 Returns 536 ------- 537 tuple 538 Complete LSTM output (output, hidden) where output has been modified. 539 """ 540 combined_last_hidden = args[0] 541 return None, combined_last_hidden
Recombine dendrite-modified last hidden, and append None just to maintain output format
Parameters
- *args (tuple): Contains the dendrite-modified output state.
- **kwargs (dict): Unused keyword arguments.
Returns
- tuple: Complete LSTM output (output, hidden) where output has been modified.
543 def pre_d(self, *args, **kwargs): 544 """ 545 LSTM input is just the tensor which also goes to the dendrite 546 547 Parameters 548 ---------- 549 *args : 550 Input tensor 551 **kwargs : dict 552 Empty 553 554 Returns 555 ------- 556 tuple 557 (output, hidden) 558 """ 559 return args, kwargs
LSTM input is just the tensor which also goes to the dendrite
Parameters
- *args (): Input tensor
- **kwargs (dict): Empty
Returns
- tuple: (output, hidden)
561 def post_d(self, *args, **kwargs): 562 """ 563 Extract extract the dendrites last hidden to combine with neurons. 564 565 Parameters 566 ---------- 567 *args : tuple 568 Contains dendrite LSTM output tuple (output, hidden). 569 **kwargs : dict 570 Unused keyword arguments. 571 572 Returns 573 ------- 574 torch.Tensor 575 Output state to be added to the neuron output. 576 """ 577 ignored_output = args[0][0] 578 last_hidden = args[0][1][-1] 579 return last_hidden
Extract extract the dendrites last hidden to combine with neurons.
Parameters
- *args (tuple): Contains dendrite LSTM output tuple (output, hidden).
- **kwargs (dict): Unused keyword arguments.
Returns
- torch.Tensor: Output state to be added to the neuron output.
581 def clear_processor(self): 582 """Clear processor state. 583 584 Notes 585 ----- 586 This processor keeps no persistent internal state. 587 588 Parameters 589 ---------- 590 None 591 592 Returns 593 ------- 594 None 595 This function does not return a value. 596 """ 597 # Nothing is stored 598 pass
Clear processor state.
Notes
This processor keeps no persistent internal state.
Parameters
- None
Returns
- None: This function does not return a value.
600class ResNetPAI(nn.Module): 601 """PB-compatible ResNet wrapper. 602 603 All normalization layers should be wrapped in a PAISequential, or other 604 wrapped module. When working with a predefined model the following shows 605 an example of how to create a module for modules_to_replace. 606 """ 607 608 def __init__(self, other_resnet): 609 """Initialize ResNetPAI from existing ResNet model. 610 611 Parameters 612 ---------- 613 *args : other_resnet : torchvision.models.resnet.ResNet 614 An existing ResNet model to convert to PAI-compatible format. 615 """ 616 super(ResNetPAI, self).__init__() 617 618 # For the most part, just copy the exact values from the original module 619 self._norm_layer = other_resnet._norm_layer 620 self.inplanes = other_resnet.inplanes 621 self.dilation = other_resnet.dilation 622 self.groups = other_resnet.groups 623 self.base_width = other_resnet.base_width 624 625 # For the component to be changed, define a PAISequential with the old 626 # modules included 627 self.b1 = GPA.PAISequential([other_resnet.conv1, other_resnet.bn1]) 628 629 self.relu = other_resnet.relu 630 self.maxpool = other_resnet.maxpool 631 632 for i in range(1, 5): 633 layer_name = "layer" + str(i) 634 original_layer = getattr(other_resnet, layer_name) 635 pb_layer = self._make_layer_pb(original_layer, other_resnet, i) 636 setattr(self, layer_name, pb_layer) 637 638 self.avgpool = other_resnet.avgpool 639 self.fc = other_resnet.fc 640 641 def _make_layer_pb(self, other_block_set, other_resnet, block_id): 642 """Convert ResNet layer blocks to PB-compatible format. 643 644 Parameters 645 ---------- 646 other_block_set : torch.vision.models.resnet.any_block 647 A set of blocks from the original ResNet model. 648 other_resnet : torchvision.models.resnet.ResNet 649 The original ResNet model. 650 block_id : int 651 The layer number being converted. 652 Returns 653 ------- 654 nn.Sequential 655 A sequential container with the converted blocks. 656 """ 657 layers = [] 658 for i in range(len(other_block_set)): 659 block_type = type(other_block_set[i]) 660 if block_type == resnet_pt.BasicBlock: 661 layers.append(other_block_set[i]) 662 elif block_type == resnet_pt.Bottleneck: 663 layers.append(other_block_set[i]) 664 else: 665 print( 666 "Your resnet uses a block type that has not been " 667 "accounted for. Customization might be required." 668 ) 669 layer_name = "layer" + str(block_id) 670 print(type(getattr(other_resnet, layer_name))) 671 pdb.set_trace() 672 return nn.Sequential(*layers) 673 674 def _forward_impl(self, x): 675 """Implementation of the forward pass. 676 677 Parameters 678 ---------- 679 x : torch.Tensor 680 Input tensor to the network. 681 682 Returns 683 ------- 684 torch.Tensor 685 Output tensor from the network. 686 """ 687 # Modified b1 rather than conv1 and bn1 688 x = self.b1(x) 689 # Rest of forward remains the same 690 x = F.relu(x) 691 x = self.maxpool(x) 692 693 x = self.layer1(x) 694 x = self.layer2(x) 695 x = self.layer3(x) 696 x = self.layer4(x) 697 698 x = self.avgpool(x) 699 x = torch.flatten(x, 1) 700 x = self.fc(x) 701 702 return x 703 704 def forward(self, x): 705 """Forward pass through the network. 706 707 Parameters 708 ---------- 709 x : torch.Tensor 710 Input tensor to the network. 711 712 Returns 713 ------- 714 torch.Tensor 715 Output tensor from the network. 716 """ 717 return self._forward_impl(x)
PB-compatible ResNet wrapper.
All normalization layers should be wrapped in a PAISequential, or other wrapped module. When working with a predefined model the following shows an example of how to create a module for modules_to_replace.
608 def __init__(self, other_resnet): 609 """Initialize ResNetPAI from existing ResNet model. 610 611 Parameters 612 ---------- 613 *args : other_resnet : torchvision.models.resnet.ResNet 614 An existing ResNet model to convert to PAI-compatible format. 615 """ 616 super(ResNetPAI, self).__init__() 617 618 # For the most part, just copy the exact values from the original module 619 self._norm_layer = other_resnet._norm_layer 620 self.inplanes = other_resnet.inplanes 621 self.dilation = other_resnet.dilation 622 self.groups = other_resnet.groups 623 self.base_width = other_resnet.base_width 624 625 # For the component to be changed, define a PAISequential with the old 626 # modules included 627 self.b1 = GPA.PAISequential([other_resnet.conv1, other_resnet.bn1]) 628 629 self.relu = other_resnet.relu 630 self.maxpool = other_resnet.maxpool 631 632 for i in range(1, 5): 633 layer_name = "layer" + str(i) 634 original_layer = getattr(other_resnet, layer_name) 635 pb_layer = self._make_layer_pb(original_layer, other_resnet, i) 636 setattr(self, layer_name, pb_layer) 637 638 self.avgpool = other_resnet.avgpool 639 self.fc = other_resnet.fc
Initialize ResNetPAI from existing ResNet model.
Parameters
- *args : other_resnet (torchvision.models.resnet.ResNet): An existing ResNet model to convert to PAI-compatible format.
704 def forward(self, x): 705 """Forward pass through the network. 706 707 Parameters 708 ---------- 709 x : torch.Tensor 710 Input tensor to the network. 711 712 Returns 713 ------- 714 torch.Tensor 715 Output tensor from the network. 716 """ 717 return self._forward_impl(x)
Forward pass through the network.
Parameters
- x (torch.Tensor): Input tensor to the network.
Returns
- torch.Tensor: Output tensor from the network.
720class ResNetPAIPreFC(nn.Module): 721 """PB-compatible ResNet wrapper. 722 723 All normalization layers should be wrapped in a PAISequential, or other 724 wrapped module. When working with a predefined model the following shows 725 an example of how to create a module for modules_to_replace. 726 """ 727 728 def __init__(self, other_resnet): 729 """Initialize ResNetPAI from existing ResNet model. 730 731 Parameters 732 ---------- 733 *args : other_resnet : torchvision.models.resnet.ResNet 734 An existing ResNet model to convert to PAI-compatible format. 735 """ 736 super(ResNetPAIPreFC, self).__init__() 737 738 # For the most part, just copy the exact values from the original module 739 self._norm_layer = other_resnet._norm_layer 740 self.inplanes = other_resnet.inplanes 741 self.dilation = other_resnet.dilation 742 self.groups = other_resnet.groups 743 self.base_width = other_resnet.base_width 744 745 # For the component to be changed, define a PAISequential with the old 746 # modules included 747 self.conv1 = other_resnet.conv1 748 self.bn1 = other_resnet.bn1 749 750 self.relu = other_resnet.relu 751 self.maxpool = other_resnet.maxpool 752 753 for i in range(1, 5): 754 layer_name = "layer" + str(i) 755 original_layer = getattr(other_resnet, layer_name) 756 pb_layer = self._make_layer_pb(original_layer, other_resnet, i) 757 setattr(self, layer_name, pb_layer) 758 759 self.avgpool = other_resnet.avgpool 760 761 # Create pre_fc layer with dimensions matching layer4 output (same as fc input) 762 fc_in_features = other_resnet.fc.in_features 763 self.pre_fc = nn.Linear(fc_in_features, fc_in_features) 764 765 self.fc = other_resnet.fc 766 767 def _make_layer_pb(self, other_block_set, other_resnet, block_id): 768 """Convert ResNet layer blocks to PB-compatible format. 769 770 Parameters 771 ---------- 772 other_block_set : torch.vision.models.resnet.any_block 773 A set of blocks from the original ResNet model. 774 other_resnet : torchvision.models.resnet.ResNet 775 The original ResNet model. 776 block_id : int 777 The layer number being converted. 778 Returns 779 ------- 780 nn.Sequential 781 A sequential container with the converted blocks. 782 """ 783 layers = [] 784 for i in range(len(other_block_set)): 785 block_type = type(other_block_set[i]) 786 if block_type == resnet_pt.BasicBlock: 787 layers.append(other_block_set[i]) 788 elif block_type == resnet_pt.Bottleneck: 789 layers.append(other_block_set[i]) 790 else: 791 print( 792 "Your resnet uses a block type that has not been " 793 "accounted for. Customization might be required." 794 ) 795 layer_name = "layer" + str(block_id) 796 print(type(getattr(other_resnet, layer_name))) 797 pdb.set_trace() 798 return nn.Sequential(*layers) 799 800 def _forward_impl(self, x): 801 """Implementation of the forward pass. 802 803 Parameters 804 ---------- 805 x : torch.Tensor 806 Input tensor to the network. 807 808 Returns 809 ------- 810 torch.Tensor 811 Output tensor from the network. 812 """ 813 # Modified b1 rather than conv1 and bn1 814 x = self.conv1(x) 815 x = self.bn1(x) 816 # Rest of forward remains the same 817 x = F.relu(x) 818 x = self.maxpool(x) 819 820 x = self.layer1(x) 821 x = self.layer2(x) 822 x = self.layer3(x) 823 x = self.layer4(x) 824 825 x = self.avgpool(x) 826 x = torch.flatten(x, 1) 827 x = self.pre_fc(x) 828 x = F.relu(x) 829 x = self.fc(x) 830 831 return x 832 833 def forward(self, x): 834 """Forward pass through the network. 835 836 Parameters 837 ---------- 838 x : torch.Tensor 839 Input tensor to the network. 840 841 Returns 842 ------- 843 torch.Tensor 844 Output tensor from the network. 845 """ 846 return self._forward_impl(x)
PB-compatible ResNet wrapper.
All normalization layers should be wrapped in a PAISequential, or other wrapped module. When working with a predefined model the following shows an example of how to create a module for modules_to_replace.
728 def __init__(self, other_resnet): 729 """Initialize ResNetPAI from existing ResNet model. 730 731 Parameters 732 ---------- 733 *args : other_resnet : torchvision.models.resnet.ResNet 734 An existing ResNet model to convert to PAI-compatible format. 735 """ 736 super(ResNetPAIPreFC, self).__init__() 737 738 # For the most part, just copy the exact values from the original module 739 self._norm_layer = other_resnet._norm_layer 740 self.inplanes = other_resnet.inplanes 741 self.dilation = other_resnet.dilation 742 self.groups = other_resnet.groups 743 self.base_width = other_resnet.base_width 744 745 # For the component to be changed, define a PAISequential with the old 746 # modules included 747 self.conv1 = other_resnet.conv1 748 self.bn1 = other_resnet.bn1 749 750 self.relu = other_resnet.relu 751 self.maxpool = other_resnet.maxpool 752 753 for i in range(1, 5): 754 layer_name = "layer" + str(i) 755 original_layer = getattr(other_resnet, layer_name) 756 pb_layer = self._make_layer_pb(original_layer, other_resnet, i) 757 setattr(self, layer_name, pb_layer) 758 759 self.avgpool = other_resnet.avgpool 760 761 # Create pre_fc layer with dimensions matching layer4 output (same as fc input) 762 fc_in_features = other_resnet.fc.in_features 763 self.pre_fc = nn.Linear(fc_in_features, fc_in_features) 764 765 self.fc = other_resnet.fc
Initialize ResNetPAI from existing ResNet model.
Parameters
- *args : other_resnet (torchvision.models.resnet.ResNet): An existing ResNet model to convert to PAI-compatible format.
833 def forward(self, x): 834 """Forward pass through the network. 835 836 Parameters 837 ---------- 838 x : torch.Tensor 839 Input tensor to the network. 840 841 Returns 842 ------- 843 torch.Tensor 844 Output tensor from the network. 845 """ 846 return self._forward_impl(x)
Forward pass through the network.
Parameters
- x (torch.Tensor): Input tensor to the network.
Returns
- torch.Tensor: Output tensor from the network.