perforatedai.utils_perforatedai
1# Copyright (c) 2025 Perforated AI 2 3import torch 4import torch.nn as nn 5import torch.nn.init as init 6import torch.nn.functional as F 7import math 8import sys 9import numpy as np 10import pdb 11import os 12import time 13import warnings 14from collections import defaultdict 15 16from perforatedai import globals_perforatedai as GPA 17from perforatedai import modules_perforatedai as PA 18from perforatedai import tracker_perforatedai as TPA 19from perforatedai import clean_perforatedai as CL 20from perforatedai import blockwise_perforatedai as BPA 21from perforatedai import network_perforatedai as NPA 22 23try: 24 from dashboard_utils.event_emitter import emitter as _dashboard_emitter 25except ImportError: 26 _dashboard_emitter = None 27 28try: 29 from perforatedbp import utils_pbp as UPB 30 from perforatedbp import modules_pbp as MPB 31except ModuleNotFoundError as e: 32 # Only pass if perforatedbp package itself is missing 33 if e.name == "perforatedbp": 34 pass 35 else: 36 # perforatedbp exists but is missing a dependency 37 raise 38 39import copy 40 41from safetensors.torch import load_file 42from safetensors.torch import save_file 43from safetensors.torch import safe_open 44 45 46def perforate_model( 47 model, 48 doing_pai=True, 49 save_name="", 50 making_graphs=True, 51 maximizing_score=True, 52 num_classes=10000000000, 53 values_per_train_epoch=-1, 54 values_per_val_epoch=-1, 55 zooming_graph=True, 56): 57 """Main function to initialize the network to add dendrites 58 59 This kicks off the entire Perforated AI process to add 60 the scaffolding to the network to be able to add dendrites 61 62 Parameters 63 ---------- 64 model : nn.Module 65 The neural network model to initialize. 66 doing_pai : bool, optional 67 Whether to actually add dendrites, by default True 68 save_name : str, optional 69 The name to save the model under, by default "PAI" 70 making_graphs : bool, optional 71 Whether to create graphs during training, by default True 72 maximizing_score : bool, optional 73 Whether to maximize the score during training, by default True 74 setting to false is for when the score is a loss to be minimized 75 num_classes : int, optional 76 The number of output classes, unused in current version 77 values_per_train_epoch : int, optional 78 The number of values to look back for graphing 79 during training, by default -1 (all values). 80 values_per_val_epoch : int, optional 81 The number of values to look back for graphing 82 during validation, by default -1 (all values). 83 zooming_graph : bool, optional 84 Whether to enable zooming on the graphs, by default True 85 86 Returns 87 ------- 88 model : nn.Module 89 The modified model with dendrite scaffolding added if doing_pai is True 90 91 """ 92 93 if save_name == "": 94 if GPA.pc.get_save_name() == "": 95 save_name = "PAI" 96 else: 97 save_name = GPA.pc.get_save_name() 98 99 if "/" in save_name: 100 print( 101 f"Warning: save_name '{save_name}' contains '/'. Relative paths are not implemented yet." 102 ) 103 sys.exit(1) 104 105 sanitized_save_name = "".join( 106 ch for ch in save_name if ch.isalnum() or ch in ("_", "-", ".") 107 ) 108 if sanitized_save_name != save_name: 109 print( 110 f"Warning: save_name '{save_name}' contained spaces or special characters. " 111 f"Using '{sanitized_save_name}' instead." 112 ) 113 save_name = sanitized_save_name 114 115 if save_name == "": 116 print("Warning: save_name became empty after sanitization. Using 'PAI'.") 117 save_name = "PAI" 118 119 120 GPA.pai_tracker = TPA.PAINeuronModuleTracker( 121 doing_pai=doing_pai, save_name=save_name 122 ) 123 GPA.pc.set_save_name(save_name) 124 if _dashboard_emitter is not None: 125 _dashboard_emitter.emit_run_start(GPA.pc, save_name) 126 model = GPA.pai_tracker.initialize( 127 model, 128 doing_pai=doing_pai, 129 save_name=save_name, 130 making_graphs=making_graphs, 131 maximizing_score=maximizing_score, 132 num_classes=num_classes, 133 values_per_train_epoch=-values_per_train_epoch, 134 values_per_val_epoch=values_per_val_epoch, 135 zooming_graph=zooming_graph, 136 ) 137 138 # Save config after perforation 139 if not GPA.pc.get_testing_dendrite_capacity(): 140 import os 141 GPA.pc.save_config(os.path.join(os.getcwd(), save_name, f"{save_name}_config.json")) 142 143 return model 144 145 146def get_pai_modules(net, depth, seen_ids=None): 147 """Get a list of all neuron modules 148 149 Parameters 150 ---------- 151 net : nn.Module 152 The module to search. 153 depth : int 154 The current depth in the recursion. 155 156 Returns 157 ------- 158 list 159 A list of all PAI neuron modules found in the network. 160 161 """ 162 if seen_ids is None: 163 seen_ids = set() 164 all_members = net.__dir__() 165 this_list = [] 166 if issubclass(type(net), nn.Sequential) or issubclass(type(net), nn.ModuleList): 167 for submodule_id, layer in net.named_children(): 168 # If there is a self pointer ignore it 169 if net.get_submodule(submodule_id) is net: 170 continue 171 if type(net.get_submodule(submodule_id)) is PA.PAINeuronModule: 172 module = net.get_submodule(submodule_id) 173 if id(module) in seen_ids: 174 continue 175 seen_ids.add(id(module)) 176 this_list = this_list + [module] 177 else: 178 this_list = this_list + get_pai_modules( 179 net.get_submodule(submodule_id), depth + 1, seen_ids 180 ) 181 else: 182 for member in all_members: 183 if isinstance(getattr(type(net), member, None), property): 184 continue 185 # if the getter fails or it is a self pointer ignore it 186 try: 187 if getattr(net, member, None) is net: 188 continue 189 except: 190 continue 191 if type(getattr(net, member, None)) is PA.PAINeuronModule: 192 module = getattr(net, member) 193 if id(module) in seen_ids: 194 continue 195 seen_ids.add(id(module)) 196 this_list = this_list + [module] 197 elif ( 198 issubclass(type(getattr(net, member, None)), nn.Module) 199 or issubclass(type(getattr(net, member, None)), nn.Sequential) 200 or issubclass(type(getattr(net, member, None)), nn.ModuleList) 201 ): 202 this_list = this_list + get_pai_modules( 203 getattr(net, member), depth + 1, seen_ids 204 ) 205 206 return this_list 207 208 209def get_tracked_modules(net, depth, seen_ids=None): 210 """Get a list of all tracked modules 211 212 Parameters 213 ---------- 214 net : nn.Module 215 The module to search. 216 depth : int 217 The current depth in the recursion. 218 219 Returns 220 ------- 221 list 222 A list of all tracked modules found in the network. 223 224 """ 225 if seen_ids is None: 226 seen_ids = set() 227 all_members = net.__dir__() 228 this_list = [] 229 if issubclass(type(net), nn.Sequential) or issubclass(type(net), nn.ModuleList): 230 for submodule_id, layer in net.named_children(): 231 if net.get_submodule(submodule_id) is net: 232 continue 233 if type(net.get_submodule(submodule_id)) is PA.TrackedNeuronModule: 234 module = net.get_submodule(submodule_id) 235 if id(module) in seen_ids: 236 continue 237 seen_ids.add(id(module)) 238 this_list = this_list + [module] 239 else: 240 this_list = this_list + get_tracked_modules( 241 net.get_submodule(submodule_id), depth + 1, seen_ids 242 ) 243 else: 244 for member in all_members: 245 if isinstance(getattr(type(net), member, None), property): 246 continue 247 # if the getter fails or it is a self pointer ignore it 248 try: 249 if getattr(net, member, None) is net: 250 continue 251 except: 252 continue 253 if type(getattr(net, member, None)) is PA.TrackedNeuronModule: 254 module = getattr(net, member) 255 if id(module) in seen_ids: 256 continue 257 seen_ids.add(id(module)) 258 this_list = this_list + [module] 259 elif issubclass(type(getattr(net, member, None)), nn.Module): 260 this_list = this_list + get_tracked_modules( 261 getattr(net, member), depth + 1, seen_ids 262 ) 263 return this_list 264 265 266def get_pai_module_params(net, depth, seen_ids=None): 267 """Get a list of all neuron module parameters 268 269 Parameters 270 ---------- 271 net : nn.Module 272 The module to search. 273 depth : int 274 The current depth in the recursion. 275 276 Returns 277 ------- 278 list 279 A list of all parameters of neuron modules found in this module. 280 281 """ 282 283 if seen_ids is None: 284 seen_ids = set() 285 all_members = net.__dir__() 286 this_list = [] 287 if issubclass(type(net), nn.Sequential) or issubclass(type(net), nn.ModuleList): 288 for submodule_id, layer in net.named_children(): 289 if isinstance(net.get_submodule(submodule_id), PA.PAINeuronModule): # 290 module = net.get_submodule(submodule_id) 291 if id(module) in seen_ids: 292 continue 293 seen_ids.add(id(module)) 294 for param in module.parameters(): 295 if param.requires_grad: 296 this_list = this_list + [param] 297 else: 298 this_list = this_list + get_pai_module_params( 299 net.get_submodule(submodule_id), depth + 1, seen_ids 300 ) 301 else: 302 for member in all_members: 303 if isinstance(getattr(type(net), member, None), property): 304 continue 305 if getattr(net, member, None) == net: 306 continue 307 if isinstance(getattr(net, member, None), PA.PAINeuronModule): 308 module = getattr(net, member) 309 if id(module) in seen_ids: 310 continue 311 seen_ids.add(id(module)) 312 for param in module.parameters(): 313 if param.requires_grad: 314 this_list = this_list + [param] 315 elif issubclass(type(getattr(net, member, None)), nn.Module): 316 this_list = this_list + get_pai_module_params( 317 getattr(net, member), depth + 1, seen_ids 318 ) 319 return this_list 320 321 322def get_pai_network_params(net): 323 """Get a list of all neuron module parameters 324 325 Parameters 326 ---------- 327 net : nn.Module 328 The full model to search. 329 330 Returns 331 ------- 332 list 333 A list of all parameters of neuron modules found in the network. 334 335 """ 336 param_list = get_pai_module_params(net, 0) 337 return param_list 338 339 340def replace_predefined_modules(start_module): 341 """Replace a module with the module from globals list 342 343 Parameters 344 ---------- 345 start_module : nn.Module 346 The module to replace. 347 348 Returns 349 ------- 350 nn.Module 351 The replaced module. 352 353 """ 354 index = GPA.pc.get_modules_to_replace().index(type(start_module)) 355 return GPA.pc.get_replacement_modules()[index](start_module) 356 357 358def scan_module_aliases(net): 359 """Find alias module paths that point to already-seen module instances. 360 361 Parameters 362 ---------- 363 net : Any PyTorch Module. 364 365 Returns 366 ------- 367 dict[str, str] 368 Mapping of alias module paths to their canonical module paths. 369 """ 370 canonical = {} 371 aliases = {} 372 for name, module in net.named_modules(remove_duplicate=False): 373 if name == "": 374 continue 375 sub_name = "." + name 376 module_id = id(module) 377 if module_id in canonical: 378 aliases[sub_name] = canonical[module_id] 379 else: 380 canonical[module_id] = sub_name 381 return aliases 382 383 384def convert_module( 385 net, 386 depth, 387 name_so_far, 388 converted_list, 389 converted_names_list, 390 neuron_module_class, 391 tracked_module_class, 392): 393 """Recursive function to do all conversion of modules to wrappers of modules 394 395 This is the function that goes through all of the module lists from 396 the globals file and does all the conversion and replacements to 397 setup the dendrite scaffolding as instructed. 398 399 Parameters 400 ---------- 401 net : nn.Module 402 The module to convert. 403 depth : int 404 The current depth in the recursion. 405 name_so_far : str 406 The name of the module so far in the recursion. 407 converted_list : list 408 A list of already converted module ids to avoid infinite loops. 409 converted_names_list : list 410 A corresponding list to help debug duplicate conversions 411 412 Returns 413 ------- 414 nn.Module 415 The converted module. 416 417 """ 418 if GPA.pc.get_verbose(): 419 print("calling convert on %s depth %d" % (net, depth)) 420 print( 421 "calling convert on %s: %s, depth %d" 422 % (name_so_far, type(net).__name__, depth) 423 ) 424 if isinstance(net, neuron_module_class) or ( 425 (tracked_module_class is not None) and isinstance(net, tracked_module_class) 426 ): 427 if GPA.pc.get_verbose(): 428 print( 429 "This is only being called because something in your model " 430 "is pointed to twice by two different variables. Highest " 431 "thing on the list is one of the duplicates" 432 ) 433 return net 434 if depth == 0 and name_so_far == "": 435 aliases = scan_module_aliases(net) 436 existing_not_save = set(GPA.pc.get_module_names_to_not_save()) 437 aliases_to_skip = [ 438 alias for alias in aliases.keys() if alias not in existing_not_save 439 ] 440 if aliases_to_skip: 441 GPA.pc.append_module_names_to_not_save(aliases_to_skip) 442 print( 443 "Auto-detected duplicate module aliases via named_modules; " 444 "keeping first-seen paths and skipping:" 445 ) 446 for alias in aliases_to_skip: 447 print(" - %s (keeps %s)" % (alias, aliases[alias])) 448 all_members = net.__dir__() 449 if GPA.pc.get_extra_verbose(): 450 print("all members:") 451 for member in all_members: 452 print(" - %s" % member) 453 if issubclass(type(net), nn.Sequential) or issubclass(type(net), nn.ModuleList): 454 for submodule_id, layer in net.named_children(): 455 sub_name = name_so_far + "." + str(submodule_id) 456 if sub_name in GPA.pc.get_module_ids_to_track(): 457 if GPA.pc.get_verbose(): 458 print("Seq ID is in track IDs: %s" % sub_name) 459 if tracked_module_class is None: 460 continue 461 setattr( 462 net, 463 submodule_id, 464 tracked_module_class(net.get_submodule(submodule_id), sub_name), 465 ) 466 continue 467 if sub_name in GPA.pc.get_module_ids_to_perforate(): 468 if GPA.pc.get_verbose(): 469 print("Seq ID is in convert IDs: %s" % sub_name) 470 setattr( 471 net, 472 submodule_id, 473 neuron_module_class(net.get_submodule(submodule_id), sub_name), 474 ) 475 continue 476 if type(net.get_submodule(submodule_id)) in GPA.pc.get_modules_to_replace(): 477 if GPA.pc.get_verbose(): 478 print( 479 "Seq sub is in replacement module so replacing: %s" % sub_name 480 ) 481 setattr( 482 net, 483 submodule_id, 484 replace_predefined_modules(net.get_submodule(submodule_id)), 485 ) 486 if ( 487 type(net.get_submodule(submodule_id)) in GPA.pc.get_modules_to_track() 488 ) or ( 489 type(net.get_submodule(submodule_id)).__name__ 490 in GPA.pc.get_module_names_to_track() 491 ): 492 if GPA.pc.get_verbose(): 493 print( 494 "Seq sub is in tracking list so initiating tracked for: %s" 495 % sub_name 496 ) 497 if tracked_module_class is None: 498 continue 499 setattr( 500 net, 501 submodule_id, 502 tracked_module_class(net.get_submodule(submodule_id), sub_name), 503 ) 504 elif ( 505 type(net.get_submodule(submodule_id)) 506 in GPA.pc.get_modules_to_perforate() 507 or type(net.get_submodule(submodule_id)).__name__ 508 in GPA.pc.get_module_names_to_perforate() 509 ): 510 if GPA.pc.get_verbose(): 511 print( 512 "Seq sub is in conversion list so initing PAI for: " 513 "%s" % sub_name 514 ) 515 if ( 516 issubclass( 517 type(net.get_submodule(submodule_id)), 518 torch.nn.modules.batchnorm._BatchNorm, 519 ) 520 or issubclass( 521 type(net.get_submodule(submodule_id)), 522 torch.nn.modules.instancenorm._InstanceNorm, 523 ) 524 or issubclass( 525 type(net.get_submodule(submodule_id)), 526 torch.nn.modules.normalization.LayerNorm, 527 ) 528 ): 529 print( 530 "You have an unwrapped normalization layer, this " 531 "is not recommended: " + name_so_far 532 ) 533 pdb.set_trace() 534 setattr( 535 net, 536 submodule_id, 537 neuron_module_class(net.get_submodule(submodule_id), sub_name), 538 ) 539 else: 540 if net != net.get_submodule(submodule_id): 541 converted_list += [id(net.get_submodule(submodule_id))] 542 converted_names_list += [sub_name] 543 if GPA.pc.get_verbose(): 544 print( 545 "sub is module but in no lists so going deeper: %s" 546 % sub_name 547 ) 548 549 setattr( 550 net, 551 submodule_id, 552 convert_module( 553 net.get_submodule(submodule_id), 554 depth + 1, 555 sub_name, 556 converted_list, 557 converted_names_list, 558 neuron_module_class, 559 tracked_module_class, 560 ), 561 ) 562 # else: 563 # print('%s is a self pointer so skipping' % (name_so_far + '[' + str(submodule_id) + ']')) 564 elif type(net) in GPA.pc.get_modules_to_track(): 565 # print('skipping type for returning from call to: %s' % (name_so_far)) 566 return net 567 else: 568 for member in all_members: 569 if isinstance(getattr(type(net), member, None), property): 570 continue 571 # Immediately check if able to get the member, if not skip it 572 try: 573 getattr(net, member, None) 574 except: 575 continue 576 sub_name = name_so_far + "." + member 577 member_obj = getattr(net, member, None) 578 579 if isinstance(member_obj, (torch.nn.Parameter, torch.nn.parameter.Parameter)): 580 if sub_name in GPA.pc.get_parameter_ids_to_track(): 581 if GPA.pc.get_verbose(): 582 print("tracking parameter by ID: %s" % sub_name) 583 member_obj.parameter_type = "neuron" 584 member_obj.wrapped = True 585 continue 586 587 # Track module object ids once at this level so duplicate aliases are 588 # caught consistently (including direct children of the root module). 589 if isinstance(member_obj, nn.Module): 590 if id(member_obj) in converted_list: 591 original_sub_name = converted_names_list[ 592 converted_list.index(id(member_obj)) 593 ] 594 print( 595 "The following module has a duplicate pointer within " 596 "your model: %s" % sub_name 597 ) 598 print("Keeping first pointer: %s" % original_sub_name) 599 print("Skipping duplicate pointer: %s" % sub_name) 600 print( 601 "If you prefer to keep %s and skip %s, add %s to module_names_to_not_save before convert." 602 % (sub_name, original_sub_name, original_sub_name) 603 ) 604 GPA.pc.append_module_names_to_not_save([sub_name]) 605 continue 606 converted_list += [id(member_obj)] 607 converted_names_list += [sub_name] 608 if sub_name in GPA.pc.get_module_ids_to_track(): 609 if GPA.pc.get_verbose(): 610 print("Seq ID is in track IDs: %s" % sub_name) 611 if tracked_module_class is None: 612 continue 613 setattr( 614 net, member, tracked_module_class(getattr(net, member), sub_name) 615 ) 616 continue 617 if sub_name in GPA.pc.get_module_ids_to_perforate(): 618 if GPA.pc.get_verbose(): 619 print("Seq ID is in convert IDs: %s" % sub_name) 620 setattr( 621 net, member, neuron_module_class(getattr(net, member), sub_name) 622 ) 623 continue 624 if id(getattr(net, member, None)) == id(net): 625 if GPA.pc.get_verbose(): 626 print("member sub is a self pointer: %s" % sub_name) 627 continue 628 if sub_name in GPA.pc.get_module_names_to_not_save(): 629 if GPA.pc.get_verbose(): 630 print("Skipping %s during convert" % sub_name) 631 else: 632 if sub_name == ".base_model": 633 print( 634 "By default skipping base_model. See " 635 '"Safetensors Errors" section of ' 636 "customization.md to include it." 637 ) 638 continue 639 if type(getattr(net, member, None)) in GPA.pc.get_modules_to_replace(): 640 if GPA.pc.get_verbose(): 641 print("sub is in replacement module so replacing: %s" % sub_name) 642 setattr( 643 net, member, replace_predefined_modules(getattr(net, member, None)) 644 ) 645 if ( 646 type(getattr(net, member, None)) in GPA.pc.get_modules_to_track() 647 or type(getattr(net, member, None)).__name__ 648 in GPA.pc.get_module_names_to_track() 649 or sub_name in GPA.pc.get_module_ids_to_track() 650 ): 651 if GPA.pc.get_verbose(): 652 print( 653 "sub is in tracking list so initiating tracked for: %s" 654 % sub_name 655 ) 656 if tracked_module_class is None: 657 continue 658 setattr( 659 net, member, tracked_module_class(getattr(net, member), sub_name) 660 ) 661 elif ( 662 type(getattr(net, member, None)) in GPA.pc.get_modules_to_perforate() 663 or type(getattr(net, member, None)).__name__ 664 in GPA.pc.get_module_names_to_perforate() 665 or (sub_name in GPA.pc.get_module_ids_to_perforate()) 666 ): 667 if GPA.pc.get_verbose(): 668 print( 669 "sub is in conversion list so initiating PAI for: %s" % sub_name 670 ) 671 setattr( 672 net, 673 member, 674 neuron_module_class(getattr(net, member), sub_name), 675 ) 676 elif ( 677 issubclass(type(getattr(net, member, None)), nn.Module) 678 or issubclass(type(getattr(net, member, None)), nn.Sequential) 679 or issubclass(type(getattr(net, member, None)), nn.ModuleList) 680 ): 681 if net != getattr(net, member): 682 if GPA.pc.get_verbose(): 683 print( 684 "sub is module but in no lists so going deeper: %s" 685 % sub_name 686 ) 687 setattr( 688 net, 689 member, 690 convert_module( 691 getattr(net, member), 692 depth + 1, 693 sub_name, 694 converted_list, 695 converted_names_list, 696 neuron_module_class, 697 tracked_module_class, 698 ), 699 ) 700 if ( 701 issubclass( 702 type(getattr(net, member, None)), 703 torch.nn.modules.batchnorm._BatchNorm, 704 ) 705 or issubclass( 706 type(getattr(net, member, None)), 707 torch.nn.modules.instancenorm._InstanceNorm, 708 ) 709 or issubclass( 710 type(getattr(net, member, None)), 711 torch.nn.modules.normalization.LayerNorm, 712 ) 713 ): 714 if not GPA.pc.get_unwrapped_modules_confirmed(): 715 print( 716 "potentially found a norm Layer that " 717 "is not accounted for, this is not recommended: %s" % (sub_name) 718 ) 719 print( 720 "Set GPA.pc.set_unwrapped_modules_confirmed(True) to skip " 721 "this next time" 722 ) 723 print( 724 "inspect your network to " 725 "see what the module type containing this layer is." 726 ) 727 print("Then do one of the following:") 728 print( 729 " - Add the module type to " 730 "GPA.pc.get_module_names_to_perforate() to wrap it entirely" 731 ) 732 print( 733 " - If the norm layer is part of a sequential wrap " 734 "it and the previous layer in a PAISequential" 735 ) 736 print( 737 " - If you do not want to add dendrites to this " 738 "module add the type to GPA.pc.get_module_names_to_track()" 739 ) 740 pdb.set_trace() 741 else: 742 if GPA.pc.get_verbose(): 743 if member[0] != "_" or GPA.pc.get_extra_verbose() is True: 744 print("not calling convert on %s depth %d" % (member, depth)) 745 if GPA.pc.get_verbose(): 746 print("returning from call to: %s" % (name_so_far)) 747 return net 748 749 750def convert_network(net, layer_name=""): 751 """Function that calls convert_module and checks results 752 753 Parameters 754 ---------- 755 net : nn.Module 756 The network to convert. 757 layer_name : str, optional 758 The name of the layer if converting a single layer, by default "" 759 760 Returns 761 ------- 762 nn.Module 763 The converted network. 764 765 """ 766 if GPA.pc.get_perforated_backpropagation(): 767 UPB.initialize_pb() 768 MPB.set_main_parameters(net) 769 if type(net) in GPA.pc.get_modules_to_replace(): 770 net = replace_predefined_modules(net) 771 if (type(net) in GPA.pc.get_modules_to_perforate()) or ( 772 type(net).__name__ in GPA.pc.get_module_names_to_perforate() 773 ): 774 if layer_name == "": 775 print( 776 "converting a single layer without a name, add a " 777 "layer_name param to the call" 778 ) 779 sys.exit(-1) 780 net = PA.PAINeuronModule(net, layer_name) 781 else: 782 net = convert_module( 783 net, 0, "", [], [], PA.PAINeuronModule, PA.TrackedNeuronModule 784 ) 785 if GPA.pai_tracker.member_vars["doing_pai"]: 786 missed_ones = [] 787 tracked_ones = [] 788 for name, param in net.named_parameters(): 789 wrapped = "wrapped" in param.__dir__() 790 if wrapped: 791 if GPA.pc.get_verbose(): 792 print("param %s is now wrapped" % (name)) 793 else: 794 tracked = "tracked" in param.__dir__() 795 if tracked: 796 tracked_ones.append(name) 797 else: 798 missed_ones.append(name) 799 if ( 800 len(missed_ones) != 0 or len(tracked_ones) != 0 801 ) and GPA.pc.get_unwrapped_modules_confirmed() is False: 802 print( 803 "\n------------------------------------------------------------------" 804 ) 805 print( 806 "The following params are not wrapped.\n------------------------------------------------------------------" 807 ) 808 for name in tracked_ones: 809 print("." + name) 810 print( 811 "\n------------------------------------------------------------------" 812 ) 813 print( 814 "The following params are not tracked or wrapped.\n------------------------------------------------------------------" 815 ) 816 for name in missed_ones: 817 print("." + name) 818 print( 819 "\n------------------------------------------------------------------" 820 ) 821 print( 822 "Modules that are not wrapped will not have Dendrites to optimize them" 823 ) 824 print( 825 "Modules modules that are not tracked can cause errors and is NOT recommended" 826 ) 827 print( 828 "Any modules in the second list should be added to module_names_to_track" 829 ) 830 831 print( 832 "Set GPA.pc.set_unwrapped_modules_confirmed(True) to skip this next time" 833 ) 834 print( 835 "Inspect your network and see what the module types of these values are to add them to PGB.module_names_to_perforate" 836 ) 837 # If did miss some then set trace to debug 838 if len(missed_ones) != 0: 839 print( 840 "------------------------------------------------------------------\nType 'c' + enter to continue the run to confirm you do not want them to be refined" 841 ) 842 843 pdb.set_trace() 844 print("confirmed") 845 net.register_buffer("tracker_string", torch.tensor([], dtype=torch.uint8)) 846 return net 847 848 849def string_to_tensor(string): 850 """Helper function to convert a layer_tracker into a string 851 852 This is required for safetensors saving 853 854 Parameters 855 ---------- 856 string : str 857 The string to convert. 858 859 Returns 860 ------- 861 torch.Tensor 862 The converted tensor. 863 864 """ 865 ords = list(map(ord, string)) 866 ords = torch.tensor(ords, dtype=torch.uint8) 867 return ords 868 869 870def string_from_tensor(string_tensor): 871 """Convert a tensor back into a string 872 873 Parameters 874 ---------- 875 string_tensor : torch.Tensor 876 The tensor to convert. 877 878 Returns 879 ------- 880 str 881 The converted string. 882 883 """ 884 ords = string_tensor.tolist() 885 to_return = "" 886 # Doing block processing like this helps with memory errors 887 while len(ords) != 0: 888 remaining_ords = ords[100000:] 889 ords = ords[:100000] 890 to_append = "".join(map(chr, ords)) 891 to_return = to_return + to_append 892 ords = remaining_ords 893 return to_return 894 895 896def save_system(net, folder, name): 897 """Save the entire system 898 899 This saves the network itself as well as the tracker information 900 901 Parameters 902 ---------- 903 net : nn.Module 904 The network to save. 905 folder : str 906 The folder to save the network in. 907 name : str 908 The name to save the network under. 909 910 Returns 911 ------- 912 None 913 914 """ 915 if GPA.pc.get_verbose(): 916 print("saving system %s" % name) 917 temp = string_to_tensor(GPA.pai_tracker.to_string()) 918 if hasattr(net, "tracker_string"): 919 net.tracker_string = string_to_tensor(GPA.pai_tracker.to_string()).to( 920 next(net.parameters()).device 921 ) 922 else: 923 net.register_buffer( 924 "tracker_string", 925 string_to_tensor(GPA.pai_tracker.to_string()).to( 926 next(net.parameters()).device 927 ), 928 ) 929 # Before saving the tracker must be cleared to not contain pointers to the 930 # models modules 931 old_list = GPA.pai_tracker.neuron_module_vector 932 GPA.pai_tracker.neuron_module_vector = [] 933 save_net(net, folder, name) 934 GPA.pai_tracker.neuron_module_vector = old_list 935 pai_save_system(net, folder, name) 936 937 938def load_system( 939 net, 940 folder, 941 name, 942 load_from_restart=False, 943 switch_call=False, 944 load_from_manual_save=False, 945): 946 """Load the entire system 947 948 This is what should be used to load a saved system and restart training 949 950 Parameters 951 ---------- 952 net : nn.Module 953 The network to load into. 954 folder : str 955 The folder to load the network from. 956 name : str 957 The name to load the network from. 958 load_from_restart : bool, optional 959 Whether this is being loaded from an automatic restart, by default False 960 switch_call : bool, optional 961 Whether this is being called from a switch, by default False 962 load_from_manual_save : bool, optional 963 Whether this is being loaded from a manual save, by default False 964 965 Returns 966 ------- 967 nn.Module 968 The loaded network. 969 970 Notes 971 ----- 972 If you manually call save_system then load_from_manual_save should be True 973 974 """ 975 if GPA.pc.get_verbose(): 976 print("loading system %s" % name) 977 net = load_net(net, folder, name) 978 GPA.pai_tracker.reset_module_vector(net, load_from_restart) 979 980 GPA.pai_tracker.from_string(string_from_tensor(net.tracker_string)) 981 GPA.pai_tracker.saved_time = time.time() 982 GPA.pai_tracker.loaded = True 983 GPA.pai_tracker.member_vars["current_best_validation_score"] = 0 984 GPA.pai_tracker.member_vars["epoch_last_improved"] = GPA.pai_tracker.member_vars[ 985 "num_epochs_run" 986 ] 987 if GPA.pc.get_verbose(): 988 print( 989 "after loading epoch last improved is %d mode is %c" 990 % ( 991 GPA.pai_tracker.member_vars["epoch_last_improved"], 992 GPA.pai_tracker.member_vars["mode"], 993 ) 994 ) 995 996 # Saves always take place before the call to start_epoch so call it here 997 # when loading to correct off by 1 problems 998 if (not switch_call) and (not load_from_manual_save): 999 GPA.pai_tracker.start_epoch(internal_call=True) 1000 return net 1001 1002 1003def load_pretrained_model( 1004 net, 1005 folder, 1006 name, 1007 remove_dendrite_scaffolding=False, 1008): 1009 """Load a pretrained perforated model and reset tracker for fresh training. 1010 1011 This function loads a pretrained model's weights and dendrite structure while 1012 resetting all tracker state (epochs, switch history, etc.) to start training 1013 from scratch on a new task. This is useful for transfer learning where you want 1014 pretrained weights but need fresh training dynamics. 1015 1016 Parameters 1017 ---------- 1018 net : nn.Module 1019 The network to load into. 1020 folder : str 1021 The folder containing the pretrained model. 1022 name : str 1023 The name of the checkpoint to load (e.g., 'best_model', 'beforeSwitch_0'). 1024 remove_dendrite_scaffolding : bool, optional 1025 If True, removes dendrite scaffolding for inference or finetuning without 1026 adding more dendrites using blockwise_network and refresh_net. Default False. 1027 1028 Returns 1029 ------- 1030 nn.Module 1031 The loaded network with reset tracker state. 1032 1033 Examples 1034 -------- 1035 Load pretrained weights for continued dendrite training: 1036 >>> model = load_pretrained_model(model, "pretrained-prefc", "beforeSwitch_0") 1037 1038 Load pretrained weights for finetuning without adding more dendrites: 1039 >>> model = load_pretrained_model(model, "pretrained-prefc", "best_model", 1040 ... remove_dendrite_scaffolding=True) 1041 1042 Notes 1043 ----- 1044 This function: 1045 - Loads model weights and dendrite structure from checkpoint 1046 - Resets all epoch counters to -1 (will become 0 after first start_epoch) 1047 - Resets switch history and validation score tracking 1048 - Clears accuracy/loss history arrays 1049 - Optionally removes dendrite scaffolding (no more dendrite additions) 1050 1051 The tracker is reset to behave as if starting fresh training, while keeping 1052 the learned weights and dendrite structure from the pretrained model. 1053 """ 1054 from perforatedai import globals_perforatedai as GPA 1055 1056 if GPA.pc.get_verbose(): 1057 print(f"Loading pretrained model from {folder}/{name}") 1058 1059 # Load the model weights and dendrite structure 1060 net = load_system(net, folder, name, load_from_manual_save=True) 1061 1062 if GPA.pc.get_verbose(): 1063 print("Resetting tracker state for fresh training...") 1064 1065 # Reset structural training state to true initial values. 1066 # Keeping pretrained architecture/weights while zeroing cycle counters avoids 1067 # stale dendrite bookkeeping referencing empty score buffers. 1068 GPA.pai_tracker.reset_module_vector(net, load_from_restart=True) 1069 GPA.pai_tracker.member_vars["mode"] = "n" 1070 GPA.pai_tracker.member_vars["num_dendrites_added"] = 0 1071 GPA.pai_tracker.member_vars["num_dendrites_integrated"] = 0 1072 GPA.pai_tracker.member_vars["num_cycles"] = 0 1073 GPA.pai_tracker.member_vars["num_dendrite_tries"] = 0 1074 GPA.pai_tracker.member_vars["current_n_set_global_best"] = True 1075 1076 # Reset epoch counters 1077 GPA.pai_tracker.member_vars["num_epochs_run"] = -1 1078 GPA.pai_tracker.member_vars["total_epochs_run"] = -1 1079 GPA.pai_tracker.member_vars["epoch_last_improved"] = 0 1080 GPA.pai_tracker.member_vars["last_switch"] = 0 1081 GPA.pai_tracker.member_vars["manual_train_switch"] = False 1082 1083 # Reset switch history 1084 GPA.pai_tracker.member_vars["switch_epochs"] = [] 1085 GPA.pai_tracker.member_vars["n_switch_epochs"] = [] 1086 GPA.pai_tracker.member_vars["p_switch_epochs"] = [] 1087 GPA.pai_tracker.member_vars["param_counts"] = [] 1088 1089 # Reset validation scores and tracking 1090 GPA.pai_tracker.member_vars["current_best_validation_score"] = 0 1091 GPA.pai_tracker.member_vars["global_best_validation_score"] = 0 1092 GPA.pai_tracker.member_vars["running_accuracy"] = 0 1093 1094 # Clear accuracy/loss history arrays 1095 GPA.pai_tracker.member_vars["accuracies"] = [] 1096 GPA.pai_tracker.member_vars["last_improved_accuracies"] = [] 1097 GPA.pai_tracker.member_vars["test_accuracies"] = [] 1098 GPA.pai_tracker.member_vars["n_accuracies"] = [] 1099 GPA.pai_tracker.member_vars["p_accuracies"] = [] 1100 GPA.pai_tracker.member_vars["running_accuracies"] = [] 1101 GPA.pai_tracker.member_vars["training_loss"] = [] 1102 GPA.pai_tracker.member_vars["training_learning_rates"] = [] 1103 GPA.pai_tracker.member_vars["test_scores"] = [] 1104 1105 # Clear extra scores 1106 GPA.pai_tracker.member_vars["extra_scores"] = {} 1107 GPA.pai_tracker.member_vars["extra_scores_without_graphing"] = {} 1108 GPA.pai_tracker.member_vars["n_extra_scores"] = {} 1109 1110 # Keep per-layer dendrite score buffers initialized by reset_module_vector. 1111 1112 # Clear timing arrays 1113 GPA.pai_tracker.member_vars["n_epoch_times"] = [] 1114 GPA.pai_tracker.member_vars["p_epoch_times"] = [] 1115 GPA.pai_tracker.member_vars["n_train_times"] = [] 1116 GPA.pai_tracker.member_vars["p_train_times"] = [] 1117 GPA.pai_tracker.member_vars["n_val_times"] = [] 1118 GPA.pai_tracker.member_vars["p_val_times"] = [] 1119 1120 # Clear overwritten tracking 1121 GPA.pai_tracker.member_vars["overwritten_extras"] = [] 1122 GPA.pai_tracker.member_vars["overwritten_vals"] = [] 1123 GPA.pai_tracker.member_vars["overwritten_epochs"] = 0 1124 1125 # Reset learning rate search state 1126 GPA.pai_tracker.member_vars["initial_lr_test_epoch_count"] = -1 1127 GPA.pai_tracker.member_vars["current_n_learning_rate_initial_skip_steps"] = 0 1128 GPA.pai_tracker.member_vars["last_max_learning_rate_steps"] = 0 1129 GPA.pai_tracker.member_vars["last_max_learning_rate_value"] = -1 1130 GPA.pai_tracker.member_vars["current_cycle_lr_max_scores"] = [] 1131 GPA.pai_tracker.member_vars["current_step_count"] = 0 1132 GPA.pai_tracker.member_vars["committed_to_initial_rate"] = True 1133 GPA.pai_tracker.member_vars["best_mean_score_improved_this_epoch"] = 0 1134 GPA.pai_tracker.member_vars["step_status"] = TPA.STEP_CLEARED 1135 1136 # Reset saved time 1137 GPA.pai_tracker.start_time = time.time() 1138 GPA.pai_tracker.saved_time = 0 1139 1140 # Match tracker initialization behavior so first validation uses epoch 0. 1141 GPA.pai_tracker.start_epoch(internal_call=True) 1142 1143 if GPA.pc.get_verbose(): 1144 print( 1145 f"Tracker reset complete. Dendrites: {GPA.pai_tracker.member_vars['num_dendrites_integrated']}, " 1146 f"Mode: {GPA.pai_tracker.member_vars['mode']}" 1147 ) 1148 1149 # Optionally remove dendrite scaffolding 1150 if remove_dendrite_scaffolding: 1151 if GPA.pc.get_verbose(): 1152 print("Removing dendrite scaffolding (no dendrite additions)...") 1153 1154 from perforatedai import blockwise_perforatedai as BPA 1155 from perforatedai import clean_perforatedai as CPA 1156 1157 net = BPA.blockwise_network(net) 1158 net = CPA.refresh_net(net) 1159 1160 if GPA.pc.get_verbose(): 1161 print("Dendrite scaffolding removed. Model ready for inference or finetuning.") 1162 1163 return net 1164 1165 1166import json 1167from collections import defaultdict 1168from safetensors.torch import save_file, safe_open 1169import torch 1170 1171 1172def save_model_with_weight_tying(model, filepath): 1173 """Save model with safetensors while handling weight tying automatically 1174 1175 Parameters 1176 ---------- 1177 model : Any PyTorch Module. 1178 filepath : Path to filename. 1179 1180 Returns 1181 ------- 1182 dict[str, str] 1183 Mapping from secondary parameter keys to their primary tied key. 1184 """ 1185 state_dict = model.state_dict() 1186 1187 # Find all weight tied parameters 1188 tensor_to_keys = defaultdict(list) 1189 for key, tensor in state_dict.items(): 1190 # Use tensor data pointer as unique identifier 1191 tensor_id = tensor.data_ptr() 1192 tensor_to_keys[tensor_id].append(key) 1193 1194 # Find tied weights (tensors referenced by multiple keys) 1195 tied_weights = {} 1196 keys_to_remove = set() 1197 for tensor_id, keys in tensor_to_keys.items(): 1198 if len(keys) > 1 and not tensor_id == 0: 1199 # Multiple keys reference the same tensor - this is weight tying 1200 # Sort keys for deterministic ordering 1201 keys = sorted(keys) 1202 primary_key = keys[0] # Keep the first key 1203 for secondary_key in keys[1:]: 1204 tied_weights[secondary_key] = primary_key 1205 keys_to_remove.add(secondary_key) 1206 1207 # Remove tied weights from state_dict (keep only primary references) 1208 filtered_state_dict = { 1209 k: v for k, v in state_dict.items() if k not in keys_to_remove 1210 } 1211 1212 # Create metadata for weight tying information 1213 metadata = {} 1214 if tied_weights: 1215 # Store weight tying info as JSON string in metadata 1216 metadata["weight_tying"] = json.dumps(tied_weights) 1217 save_file(filtered_state_dict, filepath, metadata=metadata) 1218 print(f"Saved model with {len(tied_weights)} weight tying relationships") 1219 return tied_weights 1220 1221 1222def load_model_with_weight_tying(model, filepath): 1223 """Load model from safetensors while restoring weight tying 1224 1225 Parameters 1226 ---------- 1227 model : Any PyTorch module. 1228 filepath : Path to model file. 1229 1230 Returns 1231 ------- 1232 Loaded model. 1233 """ 1234 with safe_open(filepath, framework="pt") as f: 1235 metadata = f.metadata() 1236 state_dict = {key: f.get_tensor(key) for key in f.keys()} 1237 1238 # Restore weight tying if metadata exists 1239 tied_weights = {} 1240 if metadata and "weight_tying" in metadata: 1241 tied_weights = json.loads(metadata["weight_tying"]) 1242 for secondary_key, primary_key in tied_weights.items(): 1243 if primary_key in state_dict: 1244 # Restore the tied reference 1245 state_dict[secondary_key] = state_dict[primary_key] 1246 print(f"Restored weight tying: {secondary_key} -> {primary_key}") 1247 1248 # Handle tracker_string loading with flexible key matching 1249 tracker_key = None 1250 if "tracker_string" in state_dict: 1251 tracker_key = "tracker_string" 1252 else: 1253 # Search for keys containing "tracker_string" 1254 tracker_keys = [key for key in state_dict.keys() if "tracker_string" in key] 1255 if len(tracker_keys) == 1: 1256 tracker_key = tracker_keys[0] 1257 elif len(tracker_keys) > 1: 1258 print(f"Error: Multiple tracker_string keys found: {tracker_keys}") 1259 pdb.set_trace() 1260 else: 1261 print("Error: No tracker_string found in state_dict") 1262 1263 if tracker_key is not None and hasattr(model, "tracker_string"): 1264 model.tracker_string = state_dict[tracker_key] 1265 1266 model.load_state_dict(state_dict) 1267 return model 1268 1269 1270def save_net(net, folder, name): 1271 """Save the network 1272 1273 This is called within save_system after the tracker has been 1274 turned into a single tensor to be saved as a part of the network 1275 1276 Parameters 1277 ---------- 1278 net : nn.Module 1279 The network to save. 1280 folder : str 1281 The folder to save the network in. 1282 name : str 1283 The name to save the network under. 1284 1285 Returns 1286 ------- 1287 None 1288 1289 """ 1290 # If running a DDP only save with first thread 1291 if "RANK" in os.environ: 1292 if int(os.environ["RANK"]) != 0: 1293 return 1294 if not os.path.isdir(folder): 1295 os.makedirs(folder) 1296 save_point = folder + "/" 1297 if not os.path.isdir(save_point): 1298 os.mkdir(save_point) 1299 for param in net.parameters(): 1300 param.data = param.data.contiguous() 1301 if GPA.pc.get_using_safe_tensors(): 1302 if GPA.pc.get_weight_tying_experimental(): 1303 save_model_with_weight_tying(net, save_point + name + ".pt") 1304 else: 1305 # Strip the . so that the naming is the same for everywhere but it works with state_dict naming 1306 not_save = [ns.lstrip('.') for ns in GPA.pc.get_module_names_to_not_save()] 1307 state_dict = {k: v for k, v in net.state_dict().items() 1308 if not any(k.startswith(ns) for ns in not_save)} 1309 save_file(state_dict, save_point + name + ".pt") 1310 else: 1311 torch.save(net, save_point + name + ".pt") 1312 1313 1314def save_pai_net(net, folder, name): 1315 """Save the final pai network 1316 1317 This can be called after training to save the final network 1318 with all scaffolding removed so only the refined weights remain 1319 1320 Parameters 1321 ---------- 1322 net : nn.Module 1323 The network to save. 1324 folder : str 1325 The folder to save the network in. 1326 name : str 1327 The name to save the network under. 1328 1329 Returns 1330 ------- 1331 None 1332 1333 """ 1334 # if running a DDP only save with first thread 1335 if "RANK" in os.environ: 1336 if int(os.environ["RANK"]) != 0: 1337 return 1338 1339 # print('calling save: %s' % name) 1340 # GPA.pai_tracker.archive_layer() 1341 # These deep copys are required or the real model will also have its layers replaced 1342 net = prepare_final_model(net) 1343 if not os.path.isdir(folder): 1344 os.makedirs(folder) 1345 save_point = folder + "/" 1346 if not os.path.isdir(save_point): 1347 os.mkdir(save_point) 1348 1349 if GPA.pc.get_using_safe_tensors(): 1350 if GPA.pc.get_weight_tying_experimental(): 1351 save_model_with_weight_tying(net, save_point + name + "_pai.pt") 1352 else: 1353 save_file(net.state_dict(), save_point + name + "_pai.pt") 1354 else: 1355 torch.save(net, save_point + name + "_pai.pt") 1356 1357 1358def save_pai_net(net, folder, name): 1359 """Save the final pai network 1360 1361 This can be called after training to save the final network 1362 with all scaffolding removed so only the refined weights remain 1363 1364 Parameters 1365 ---------- 1366 net : nn.Module 1367 The network to save. 1368 folder : str 1369 The folder to save the network in. 1370 name : str 1371 The name to save the network under. 1372 1373 Returns 1374 ------- 1375 None 1376 1377 """ 1378 # if running a DDP only save with first thread 1379 if "RANK" in os.environ: 1380 if int(os.environ["RANK"]) != 0: 1381 return 1382 1383 # print('calling save: %s' % name) 1384 # GPA.pai_tracker.archive_layer() 1385 # These deep copys are required or the real model will also have its layers replaced 1386 net = prepare_final_model(net) 1387 if not os.path.isdir(folder): 1388 os.makedirs(folder) 1389 save_point = folder + "/" 1390 if not os.path.isdir(save_point): 1391 os.mkdir(save_point) 1392 1393 if GPA.pc.get_using_safe_tensors(): 1394 if GPA.pc.get_weight_tying_experimental(): 1395 save_model_with_weight_tying(net, save_point + name + "_pai.pt") 1396 else: 1397 save_file(net.state_dict(), save_point + name + "_pai.pt") 1398 else: 1399 torch.save(net, save_point + name + "_pai.pt") 1400 1401 1402def manual_load_state_dict(model, state_dict): 1403 """Load a state dict into a model key-by-key with relaxed checks. 1404 1405 Parameters 1406 ---------- 1407 model : nn.Module 1408 Model receiving parameters. 1409 state_dict : dict 1410 Source parameter dictionary. 1411 1412 Notes 1413 ----- 1414 Keys configured in ``module_names_to_not_save`` are skipped. 1415 1416 Returns 1417 ------- 1418 None 1419 This function does not return a value. 1420 """ 1421 own_state = model.state_dict() 1422 not_save = [ns.lstrip('.') for ns in GPA.pc.get_module_names_to_not_save()] 1423 for name, param in state_dict.items(): 1424 if any(name.startswith(ns) for ns in not_save): 1425 print("skipping loading %s based on module_names_to_not_save" % name) 1426 continue 1427 if name not in own_state: 1428 print(f"Warning: {name} not found in model state_dict") 1429 continue 1430 if isinstance(param, torch.nn.Parameter): 1431 # Backwards compatibility for serialized parameters 1432 param = param.data 1433 try: 1434 own_state[name].copy_(param) 1435 except Exception as e: 1436 print(f"Error loading {name}: {e}") 1437 print("Manual load complete") 1438 1439 1440def load_net(net, folder, name): 1441 """load the network 1442 1443 This is called within load_system after the tracker has been 1444 loaded 1445 1446 Parameters 1447 ---------- 1448 net : nn.Module 1449 The network to save. 1450 folder : str 1451 The folder to save the network in. 1452 name : str 1453 The name to save the network under. 1454 1455 Returns 1456 ------- 1457 nn.Module 1458 The loaded network. 1459 1460 """ 1461 save_point = folder + "/" 1462 if GPA.pc.get_using_safe_tensors(): 1463 model_path = save_point + name + ".pt" 1464 if GPA.pc.get_weight_tying_experimental(): 1465 return load_model_with_weight_tying(net, model_path) 1466 else: 1467 try: 1468 with safe_open(model_path, framework="pt") as f: 1469 metadata = f.metadata() 1470 if metadata and "weight_tying" in metadata: 1471 return load_model_with_weight_tying(net, model_path) 1472 except Exception: 1473 pass 1474 state_dict = load_file(model_path) 1475 else: 1476 # Different versions of torch require this change 1477 try: 1478 state_dict = torch.load( 1479 save_point + name + ".pt", 1480 map_location=torch.device("cpu"), 1481 weights_only=False, 1482 ).state_dict() 1483 except: 1484 try: 1485 state_dict = torch.load( 1486 save_point + name + ".pt", map_location=torch.device("cpu") 1487 ).state_dict() 1488 except: 1489 state_dict = torch.load( 1490 save_point + name + ".pt", map_location=torch.device("cpu") 1491 ) 1492 return load_net_from_dict(net, state_dict) 1493 1494 1495def get_module_base_name(module): 1496 """Normalize a wrapped module name for state-dict key lookup. 1497 1498 Parameters 1499 ---------- 1500 module : nn.Module 1501 Module containing a ``name`` attribute. 1502 1503 Returns 1504 ------- 1505 str 1506 Base name with leading dot and optional ``module.`` prefix removed. 1507 """ 1508 module_name = module.name 1509 # This should always be true 1510 if module_name[0] == ".": 1511 # strip "." 1512 module_name = module_name[1:] 1513 # If it was a dataparallel it will also have a module at the start 1514 # so strip that for loading 1515 if module_name[:6] == "module": 1516 module_name = module_name[7:] 1517 return module_name 1518 1519 1520def load_net_from_dict(net, state_dict): 1521 """load the network 1522 1523 This is called within load_net 1524 1525 Parameters 1526 ---------- 1527 net : nn.Module 1528 The network to save. 1529 state_dict : dict 1530 The state dictionary to load. 1531 1532 Returns 1533 ------- 1534 nn.Module 1535 The loaded network. 1536 1537 """ 1538 if GPA.pc.get_verbose(): 1539 print("loading net from dict") 1540 pai_modules = get_pai_modules(net, 0) 1541 if pai_modules == []: 1542 print( 1543 "PAI load_net and load_system uses a state_dict so it must be\n" 1544 "called with a net after perforate_model has been called" 1545 ) 1546 print( 1547 "This is being flagged because you are attempting to load a model\n" 1548 "that does not have any pai_modules in it. Confirm that you are calling\n" 1549 "perforate_model on the correct model, and the same model is the one\n" 1550 "being passed into add_validation_score" 1551 ) 1552 import pdb # This needs to be here for cython for some reason. 1553 pdb.set_trace() 1554 sys.exit(-1) 1555 if GPA.pc.get_verbose(): 1556 print( 1557 "setting up arrays and simulating cycles for %d pai modules" 1558 % len(pai_modules) 1559 ) 1560 not_save = GPA.pc.get_module_names_to_not_save() 1561 for module in pai_modules: 1562 if any(module.name.startswith(ns) for ns in not_save): 1563 print("skipping loading %s based on module_names_to_not_save" % module.name) 1564 continue 1565 # Set up name to be what will be saved in the state dict 1566 module_name = get_module_base_name(module) 1567 module.clear_dendrites() 1568 for tracker in module.dendrite_module.dendrite_values: 1569 try: 1570 tracker.setup_arrays( 1571 len( 1572 state_dict[ 1573 module_name + ".dendrite_module.dendrite_values.0.shape" 1574 ] 1575 ) 1576 ) 1577 except Exception as e: 1578 print(e) 1579 print( 1580 "This value is missing from the state dict\n" 1581 "When missing this value it typically means you\n" 1582 "converted a module but didn't actually use it in\n" 1583 "your forward and backward pass." 1584 ) 1585 print("module was: %s" % module.name) 1586 print("There are many reasons this can happen:") 1587 print( 1588 "\n1 - check your model definition and forward function and " 1589 "ensure this module is being used properly" 1590 ) 1591 print( 1592 "with GPA.pc.set_verbose(True) you can confirm this is the case if\n" 1593 'you do not see a "setting d shape for" this module at the first training batch.' 1594 ) 1595 print( 1596 "If this is the case, and it is correct to not be passing data through it\n" 1597 "Set it to be a tracked module with:\n" 1598 'GPA.pc.append_module_ids_to_track(["%s"]) to leave it out ' 1599 % module.name 1600 ) 1601 print( 1602 "\n2 - This can happen if you adjusted your model " 1603 "definition after calling perforate_model" 1604 ) 1605 print( 1606 "for example with torch.compile. If the module name " 1607 "printed above does not contain all modules leading " 1608 "to the main definition" 1609 ) 1610 print( 1611 "this is likely the case for your problem. Fix by " 1612 "calling perforate_model after all other model " 1613 "initialization steps" 1614 ) 1615 first_key = next(iter(state_dict.keys())) 1616 print( 1617 "\n3 - This can happen is if the model where you called perforate_model\n" 1618 "and the model within add_validation_score are not the same. \n" 1619 "Check if the module above and .%s have the same prefix\n" 1620 % first_key 1621 ) 1622 print( 1623 "if one starts with .model or .base etc and the other does not, this is the problem." 1624 ) 1625 1626 print( 1627 "\n4 - If you are using this module but then not actually including\n" 1628 "the correct output tensor in the forward. For example\n" 1629 "if you are using an LSTM and forwarding hidden instead of otput\n" 1630 "but your processors are set up to work with output" 1631 ) 1632 print( 1633 "\n5 - if you are not properly calling backward at all." 1634 " If this is the first module in your network it is more" 1635 "likely this is the problem." 1636 "One check in these cases is to make sure you do not call an initial validation score" 1637 "before the first backward call.\nIf you do this, while testing_dendrite_capacity is True" 1638 "this error will be triggered." 1639 ) 1640 print( 1641 "\n6 - You have converted a module that is in a frozen" 1642 " part of the network and thus no gradients are flowing" 1643 ) 1644 print( 1645 "\n7 - You are running multiple experiments at once with the same save_name." 1646 " When running concurrent trials be sure to add save_name=<unique_name> to perforate_model." 1647 ) 1648 import pdb # This needs to be here for cython for some reason. 1649 pdb.set_trace() 1650 1651 # Perform as many cycles as the state dict has 1652 num_cycles = int(state_dict[module_name + ".dendrite_module.num_cycles"].item()) 1653 if num_cycles > 0: 1654 simulate_cycles(module, num_cycles, doing_pai=True) 1655 # Handle tracker_string loading with flexible key matching 1656 tracker_key = None 1657 if "tracker_string" in state_dict: 1658 tracker_key = "tracker_string" 1659 else: 1660 # Search for keys containing "tracker_string" 1661 tracker_keys = [key for key in state_dict.keys() if "tracker_string" in key] 1662 if len(tracker_keys) == 1: 1663 tracker_key = tracker_keys[0] 1664 elif len(tracker_keys) > 1: 1665 print(f"Error: Multiple tracker_string keys found: {tracker_keys}") 1666 import pdb # This needs to be here for cython for some reason. 1667 pdb.set_trace() 1668 else: 1669 print("Error: No tracker_string found in state_dict") 1670 import pdb # This needs to be here for cython for some reason. 1671 pdb.set_trace() 1672 1673 if hasattr(net, "tracker_string"): 1674 net.tracker_string = state_dict[tracker_key] 1675 else: 1676 net.register_buffer("tracker_string", state_dict[tracker_key]) 1677 try: 1678 load_result = net.load_state_dict(state_dict, strict=False) 1679 not_save_state_names = [ns.lstrip('.') for ns in not_save] 1680 1681 def is_ignored_key(key): 1682 """Check whether a state-dict key should be ignored. 1683 1684 Parameters 1685 ---------- 1686 key : str 1687 State-dict key to test. 1688 1689 Returns 1690 ------- 1691 bool 1692 ``True`` when key belongs to a not-saved namespace. 1693 """ 1694 return any(key.startswith(ns) for ns in not_save_state_names) 1695 1696 missing_keys = [key for key in load_result.missing_keys if not is_ignored_key(key)] 1697 unexpected_keys = [key for key in load_result.unexpected_keys if not is_ignored_key(key)] 1698 1699 if GPA.pc.get_strict_loading() and (missing_keys or unexpected_keys): 1700 raise RuntimeError( 1701 "Error(s) in loading state_dict for %s:\n\tMissing key(s) in state_dict: %s. \n\tUnexpected key(s) in state_dict: %s." 1702 % (type(net).__name__, missing_keys, unexpected_keys) 1703 ) 1704 except Exception as e: 1705 """ 1706 When modules have high depth to them (i.e. modules within modules not number of layers) 1707 PyTorch can have trouble loading state dicts even when they are correct. 1708 This is a workaround to manually load the state dict if this happens. 1709 """ 1710 filtered_net_keys = { 1711 key 1712 for key in net.state_dict().keys() 1713 if not any(key.startswith(ns.lstrip('.')) for ns in not_save) 1714 } 1715 if filtered_net_keys == set(state_dict.keys()): 1716 print("Attempting manual loading of state_dict") 1717 manual_load_state_dict(net, state_dict) 1718 else: 1719 print(f"Error loading state_dict: {e}") 1720 print("If the error is due to missing keys (e.g., from code changes), you can try:") 1721 print(" GPA.pc.set_strict_loading(False)") 1722 print(" Do not change this unless you are certain the missing keys are not important to load and are expected due to code changes or arch changes.") 1723 print("\ntype 'c' to print full state dicts\n") 1724 import pdb # This needs to be here for cython for some reason. 1725 pdb.set_trace() 1726 print("net state dict is:") 1727 print(net.state_dict()) 1728 print("loaded state dict is:") 1729 print(state_dict) 1730 print( 1731 "Try to check differences. Likely is caused by a module not " 1732 "being converted that should be or vice versa" 1733 ) 1734 pdb.set_trace() 1735 net.to(GPA.pc.get_device()) 1736 return net 1737 1738 1739def pai_save_system(net, folder, name): 1740 """Save the entire system with scaffolding removed 1741 1742 This is used for the final network for inference after training 1743 1744 Parameters 1745 ---------- 1746 net : nn.Module 1747 The network to save. 1748 folder : str 1749 The folder to save the network in. 1750 name : str 1751 The name to save the network under. 1752 1753 Returns 1754 ------- 1755 None 1756 1757 """ 1758 net.member_vars = {} 1759 for member_var in GPA.pai_tracker.member_vars: 1760 if member_var == "scheduler_instance" or member_var == "optimizer_instance": 1761 continue 1762 net.member_vars[member_var] = GPA.pai_tracker.member_vars[member_var] 1763 pai_save_net(net, folder, name) 1764 1765 1766def deep_copy_pai(net): 1767 """Deep copy a PAI network 1768 1769 1770 Parameters 1771 ---------- 1772 net : nn.Module 1773 The network to copy. 1774 1775 Returns 1776 ------- 1777 nn.Module 1778 The copied network. 1779 1780 Notes 1781 ---- 1782 This is required because processors must be cleared before calling copy 1783 1784 """ 1785 # Dont check this stuff if its before the perforate_model has been called and you're just copying a regular model 1786 if(GPA.pai_tracker != []): 1787 # Clear gradients before saving the model 1788 if ((GPA.pai_tracker.member_vars["optimizer_instance"]) is not None) and ( 1789 GPA.pai_tracker.member_vars["optimizer_instance"] != [] 1790 ): 1791 GPA.pai_tracker.member_vars["optimizer_instance"].zero_grad() 1792 GPA.pai_tracker.clear_all_processors() 1793 return copy.deepcopy(net) 1794 1795 1796def prepare_final_model(net): 1797 """Prepare model for final save by removing scaffolding. 1798 1799 This performs all cleanup steps to convert a PAI model with scaffolding 1800 into a clean final model ready for inference or distribution. 1801 1802 Parameters 1803 ---------- 1804 net : nn.Module 1805 The network to prepare. 1806 1807 Returns 1808 ------- 1809 nn.Module 1810 The cleaned model with scaffolding removed. 1811 """ 1812 # Deep copy and clean the model (removes scaffolding) 1813 net = deep_copy_pai(net) 1814 net = BPA.blockwise_network(net) 1815 net = deep_copy_pai(net) 1816 net = CL.refresh_net(net) 1817 1818 # Remove tracker_string (not needed for final model) 1819 if hasattr(net, "tracker_string"): 1820 del net.tracker_string 1821 1822 # Make parameters contiguous 1823 for param in net.parameters(): 1824 param.data = param.data.contiguous() 1825 1826 return net 1827 1828 1829def pai_save_net(net, folder, name): 1830 """Save the entire system with scaffolding removed 1831 1832 This is called within pai_save_system after the tracker has been 1833 turned into a single tensor to be saved as a part of the network 1834 1835 1836 Parameters 1837 ---------- 1838 net : nn.Module 1839 The network to save. 1840 folder : str 1841 The folder to save the network in. 1842 name : str 1843 The name to save the network under. 1844 1845 Returns 1846 ------- 1847 None 1848 1849 Notes 1850 ---- 1851 For open source implementation this is not as important since 1852 minimal values are already being used. 1853 1854 """ 1855 1856 if GPA.pc.get_perforated_backpropagation(): 1857 UPB.pb_save_net(net, folder, name) 1858 else: 1859 return 1860 1861 1862def simulate_cycles(module, num_cycles, doing_pai): 1863 """Simulate dendrite addition cycles 1864 1865 Simulate the back and forth processes of adding dendrites to build a 1866 pretrained dendrite model before loading weights. Required for loading 1867 dendrite save files from non dendrite initial models. 1868 1869 Parameters 1870 ---------- 1871 module : PA.PAINeuronModule 1872 The module to simulate cycles on. 1873 num_cycles : int 1874 The number of cycles to simulate. 1875 doing_pai : bool 1876 Whether to actually do the simulation. 1877 1878 Returns 1879 ------- 1880 None 1881 1882 """ 1883 1884 check_skipped = GPA.pc.get_checked_skipped_modules() 1885 if doing_pai is False: 1886 return 1887 GPA.pc.set_checked_skipped_modules(True) 1888 mode = "n" 1889 for i in range(num_cycles): 1890 if mode == "n": 1891 module.set_mode("p") 1892 module.create_new_dendrite_module() 1893 mode = "p" 1894 else: 1895 module.set_mode("n") 1896 mode = "n" 1897 GPA.pc.set_checked_skipped_modules(check_skipped) 1898 1899 1900def count_params(net): 1901 """Count the number of parameters in the network 1902 1903 If doing perforated backpropagation this calls the PB function 1904 which does not count scaffolding parameters since the final model 1905 will not have them. 1906 1907 Parameters 1908 ---------- 1909 net : nn.Module 1910 The network to count parameters in. 1911 1912 Returns 1913 ------- 1914 int 1915 The number of parameters in the network. 1916 1917 """ 1918 if GPA.pc.get_perforated_backpropagation(): 1919 return UPB.pb_count_params(net) 1920 parameters = net.named_parameters() 1921 unique_params = { 1922 p.data_ptr(): p for name, p in parameters if "parent_module" not in name 1923 }.values() 1924 return sum(p.numel() for p in unique_params) 1925 1926 1927def change_learning_modes(net, folder, name, doing_pai): 1928 """Change between neuron and dendrite learning modes 1929 1930 High level steps for entire system to switch back and forth between 1931 neuron learning and dendrite learning 1932 1933 Parameters 1934 ---------- 1935 net : nn.Module 1936 The network to change modes on. 1937 folder : str 1938 The folder to save/load the network in/from. 1939 name : str 1940 The name to save/load the network under. 1941 doing_pai : bool 1942 Whether to add dendrites when changing modes. 1943 1944 Returns 1945 ------- 1946 int 1947 The number of parameters in the network. 1948 1949 Notes 1950 ----- 1951 If doing_pai is False this just allows training to continue longer rather than early stopping 1952 1953 """ 1954 # If not adding dendrites this just allows training to continue longer with flags 1955 # every time early stopping should be occurring 1956 if doing_pai is False: 1957 GPA.pai_tracker.member_vars["switch_epochs"].append( 1958 GPA.pai_tracker.member_vars["num_epochs_run"] 1959 ) 1960 GPA.pai_tracker.member_vars["last_switch"] = GPA.pai_tracker.member_vars[ 1961 "switch_epochs" 1962 ][-1] 1963 GPA.pai_tracker.reset_vals_for_score_reset() 1964 return net 1965 if GPA.pai_tracker.member_vars["mode"] == "n": 1966 current_epoch = GPA.pai_tracker.member_vars["num_epochs_run"] 1967 overwritten_epochs = GPA.pai_tracker.member_vars["overwritten_epochs"] 1968 overwritten_extra = GPA.pai_tracker.member_vars["extra_scores"] 1969 if GPA.pc.get_drawing_pai(): 1970 overwritten_val = GPA.pai_tracker.member_vars["accuracies"] 1971 else: 1972 overwritten_val = GPA.pai_tracker.member_vars["neuron_accuracies"] 1973 """ 1974 If true don't load the best system 1975 because it will delete dendrites if the previous best was better than 1976 the current best 1977 """ 1978 if not GPA.pc.get_silent(): 1979 print("Importing best Model for switch to PA...") 1980 net = load_system(net, folder, name, switch_call=True) 1981 GPA.pai_tracker.set_dendrite_training() 1982 GPA.pai_tracker.member_vars["overwritten_epochs"] = overwritten_epochs 1983 GPA.pai_tracker.member_vars["overwritten_epochs"] += ( 1984 current_epoch - GPA.pai_tracker.member_vars["num_epochs_run"] 1985 ) 1986 GPA.pai_tracker.member_vars["total_epochs_run"] = ( 1987 GPA.pai_tracker.member_vars["num_epochs_run"] 1988 + GPA.pai_tracker.member_vars["overwritten_epochs"] 1989 ) 1990 1991 if GPA.pc.get_save_old_graph_scores(): 1992 GPA.pai_tracker.member_vars["overwritten_extras"].append(overwritten_extra) 1993 GPA.pai_tracker.member_vars["overwritten_vals"].append(overwritten_val) 1994 else: 1995 GPA.pai_tracker.member_vars["overwritten_extras"] = [overwritten_extra] 1996 GPA.pai_tracker.member_vars["overwritten_vals"] = [overwritten_val] 1997 if GPA.pc.get_drawing_pai(): 1998 GPA.pai_tracker.member_vars["n_switch_epochs"].append( 1999 GPA.pai_tracker.member_vars["num_epochs_run"] 2000 ) 2001 else: 2002 if len(GPA.pai_tracker.member_vars["switch_epochs"]) == 0: 2003 GPA.pai_tracker.member_vars["n_switch_epochs"].append( 2004 GPA.pai_tracker.member_vars["num_epochs_run"] 2005 ) 2006 else: 2007 GPA.pai_tracker.member_vars["n_switch_epochs"].append( 2008 GPA.pai_tracker.member_vars["n_switch_epochs"][-1] 2009 + ( 2010 (GPA.pai_tracker.member_vars["num_epochs_run"]) 2011 - (GPA.pai_tracker.member_vars["switch_epochs"][-1]) 2012 ) 2013 ) 2014 2015 GPA.pai_tracker.member_vars["switch_epochs"].append( 2016 GPA.pai_tracker.member_vars["num_epochs_run"] 2017 ) 2018 GPA.pai_tracker.member_vars["last_switch"] = GPA.pai_tracker.member_vars[ 2019 "switch_epochs" 2020 ][-1] 2021 2022 # Because open source version is only doing neuron training for 2023 # gradient descent dendrites, switch back to n mode right away 2024 if ( 2025 not GPA.pc.get_perforated_backpropagation() 2026 ) or GPA.pc.get_no_extra_n_modes(): 2027 net = change_learning_modes(net, folder, name, doing_pai) 2028 else: 2029 if not GPA.pc.get_silent(): 2030 print("Switching back to N...") 2031 set_best = GPA.pai_tracker.member_vars["current_n_set_global_best"] 2032 GPA.pai_tracker.set_neuron_training() 2033 if len(GPA.pai_tracker.member_vars["p_switch_epochs"]) == 0: 2034 GPA.pai_tracker.member_vars["p_switch_epochs"].append( 2035 ( 2036 (GPA.pai_tracker.member_vars["num_epochs_run"] - 1) 2037 - (GPA.pai_tracker.member_vars["switch_epochs"][-1]) 2038 ) 2039 ) 2040 else: 2041 GPA.pai_tracker.member_vars["p_switch_epochs"].append( 2042 GPA.pai_tracker.member_vars["p_switch_epochs"][-1] 2043 + ( 2044 (GPA.pai_tracker.member_vars["num_epochs_run"]) 2045 - (GPA.pai_tracker.member_vars["switch_epochs"][-1]) 2046 ) 2047 ) 2048 GPA.pai_tracker.member_vars["switch_epochs"].append( 2049 GPA.pai_tracker.member_vars["num_epochs_run"] 2050 ) 2051 GPA.pai_tracker.member_vars["last_switch"] = GPA.pai_tracker.member_vars[ 2052 "switch_epochs" 2053 ][-1] 2054 # Will be false for open source implementation 2055 if GPA.pc.get_retain_all_dendrites() or ( 2056 GPA.pc.get_learn_dendrites_live() and set_best 2057 ): 2058 if not GPA.pc.get_silent(): 2059 print( 2060 "Saving model before starting normal training to " 2061 "retain PBNodes regardless of next N Phase results" 2062 ) 2063 save_system(net, folder, name) 2064 # if its just doing P for learn PAI live then switch back immediately 2065 if GPA.pc.get_perforated_backpropagation() and GPA.pc.get_no_extra_n_modes(): 2066 net = change_learning_modes(net, folder, name, doing_pai) 2067 2068 GPA.pai_tracker.member_vars["param_counts"].append(count_params(net)) 2069 2070 return net 2071 2072 2073def find_param_name_by_id(model, param_id): 2074 """ 2075 This is only used for debugging. 2076 Return the fully-qualified parameter name (e.g. "layer1.conv.weight") 2077 for the parameter whose id matches param_id. Returns None if not found. 2078 2079 This uses model.named_parameters(), which already recurses through submodules. 2080 2081 Parameters 2082 ---------- 2083 model : Model to look for param id. 2084 param_id : pointer to a parameter. 2085 2086 Returns 2087 ------- 2088 String representing the module within the model or None if not found. 2089 """ 2090 for name, p in model.named_parameters(recurse=True): 2091 if id(p) == param_id: 2092 return "." + name 2093 return None 2094 2095 2096def add_method_delegation_to_module(wrapper_module, method_name): 2097 """Add delegating methods to a wrapper module that has a main_module attribute. 2098 2099 This adds the specified methods to the wrapper module instance so they 2100 properly delegate to the wrapped main_module. Works for any wrapper module 2101 (TrackedNeuronModule, PAINeuronModule, etc.) that has a main_module attribute. 2102 2103 Args: 2104 wrapper_module: A wrapper module instance with a main_module attribute 2105 method_name: The method name to delegate (e.g., '_gradient_checkpointing_func') 2106 2107 Parameters 2108 ---------- 2109 wrapper_module : PyTorch Module that contains a sub module. 2110 method_name : Name of method to wrap. 2111 2112 Returns 2113 ------- 2114 None 2115 """ 2116 import types 2117 2118 if hasattr(wrapper_module.main_module, method_name): 2119 # Create a delegating method that forwards to main_module 2120 def make_delegated_method(name): 2121 """Create a bound delegation function for a given attribute name. 2122 2123 Parameters 2124 ---------- 2125 name : str 2126 Attribute name to forward to ``main_module``. 2127 2128 Returns 2129 ------- 2130 callable 2131 Function that delegates access or invocation. 2132 """ 2133 def delegated_method(self, *args, **kwargs): 2134 """Delegate attribute access or method call to ``main_module``. 2135 2136 Parameters 2137 ---------- 2138 *args : tuple 2139 Positional arguments forwarded to delegated callables. 2140 **kwargs : dict 2141 Keyword arguments forwarded to delegated callables. 2142 2143 Returns 2144 ------- 2145 Any 2146 Delegated attribute value or method result. 2147 """ 2148 main_module_attr = getattr(self.main_module, name, None) 2149 if main_module_attr is None: 2150 raise AttributeError( 2151 f"'{type(self.main_module).__name__}' object has no attribute '{name}'" 2152 ) 2153 if callable(main_module_attr): 2154 return main_module_attr(*args, **kwargs) 2155 return main_module_attr 2156 2157 return delegated_method 2158 2159 # Bind it to this specific instance 2160 setattr( 2161 wrapper_module, 2162 method_name, 2163 types.MethodType(make_delegated_method(method_name), wrapper_module), 2164 ) 2165 2166 2167def apply_method_delegation_to_model(model, method_name, main_module_type): 2168 """Recursively apply method delegation to all wrapper modules with main_module in a model. 2169 2170 This traverses the entire model and adds method delegation for any module that has 2171 a main_module attribute and optionally matches specified types. 2172 2173 Args: 2174 model: The PyTorch model to traverse 2175 method_name: The method name to delegate (e.g., '_gradient_checkpointing_func') 2176 main_module_type: main_module type name to filter by. 2177 Example: 'Qwen2DecoderLayer' 2178 2179 Example: 2180 # Apply gradient checkpointing delegation to all decoder layers 2181 apply_method_delegation_to_model( 2182 model, 2183 '_gradient_checkpointing_func', 2184 main_module_type='Qwen2DecoderLayer' 2185 ) 2186 2187 Parameters 2188 ---------- 2189 model : PyTorch model. 2190 method_name : method to delegate. 2191 main_module_type : type of module that has this method. 2192 2193 Returns 2194 ------- 2195 None 2196 This function does not return a value. 2197 """ 2198 count = 0 2199 for name, module in model.named_modules(): 2200 # Check if module has main_module attribute (it's a wrapper) 2201 if hasattr(module, "main_module"): 2202 # Check if we should apply based on main_module type 2203 should_apply = True 2204 if main_module_type is not None: 2205 main_module_type_name = type(module.main_module).__name__ 2206 should_apply = main_module_type_name == main_module_type 2207 2208 if should_apply: 2209 add_method_delegation_to_module(module, method_name) 2210 count += 1 2211 2212 print(f"[PAI] Applied method delegation to {count} wrapper module instances") 2213 2214 2215def make_json_serializable(obj): 2216 """Recursively convert non-JSON-serializable objects to strings. 2217 2218 Parameters 2219 ---------- 2220 obj : any 2221 The object to convert 2222 2223 Returns 2224 ------- 2225 Any 2226 JSON-serializable version of the object 2227 """ 2228 if isinstance(obj, (str, int, float, bool, type(None))): 2229 return obj 2230 elif isinstance(obj, dict): 2231 return {k: make_json_serializable(v) for k, v in obj.items()} 2232 elif isinstance(obj, (list, tuple)): 2233 return [make_json_serializable(item) for item in obj] 2234 else: 2235 # Convert non-serializable types to string 2236 return str(obj) 2237 2238 2239def extract_gpa_config(): 2240 """Extract all configuration from GPA.pc by calling all get_* methods. 2241 2242 Returns 2243 ------- 2244 dict[str, Any] 2245 Dictionary with all GPA.pc configuration values and type metadata 2246 2247 Examples 2248 -------- 2249 >>> config = extract_gpa_config() 2250 >>> # Returns: {'max_dendrites': 10, 'device': 'cuda', '_types': {...}} 2251 2252 Parameters 2253 ---------- 2254 None 2255 2256 """ 2257 config = {} 2258 config_types = {} 2259 2260 # Get all attributes from GPA.pc 2261 for attr_name in dir(GPA.pc): 2262 # Check if it starts with 'get_' 2263 if attr_name.startswith("get_"): 2264 try: 2265 # Get the method 2266 method = getattr(GPA.pc, attr_name) 2267 2268 # Check if it's callable 2269 if callable(method): 2270 # Call it and store result with key as name without 'get_' 2271 key = attr_name[4:] # Remove 'get_' prefix 2272 value = method() 2273 2274 # Check if this is an array (has corresponding append_ method) 2275 append_method_name = f"append_{key}" 2276 is_array = hasattr(GPA.pc, append_method_name) 2277 2278 if is_array and isinstance(value, (list, tuple)): 2279 # Store array element type 2280 if len(value) > 0: 2281 element_type = type(value[0]).__name__ 2282 else: 2283 element_type = None # empty array, no conversion needed 2284 config_types[key] = { 2285 "is_array": True, 2286 "element_type": element_type, 2287 } 2288 else: 2289 # Store value type 2290 config_types[key] = { 2291 "is_array": False, 2292 "type": type(value).__name__, 2293 } 2294 2295 # Make sure value is JSON serializable 2296 config[key] = make_json_serializable(value) 2297 except Exception as e: 2298 # Skip if method fails 2299 if GPA.pc.get_verbose(): 2300 print(f"Skipping {attr_name}: {e}") 2301 continue 2302 2303 # Add types metadata to config 2304 config["_types"] = config_types 2305 2306 return config 2307 2308 2309def convert_to_type(value, type_name): 2310 """Convert a value to the specified type. 2311 2312 Parameters 2313 ---------- 2314 value : any 2315 The value to convert 2316 type_name : str 2317 The target type name 2318 2319 Returns 2320 ------- 2321 Any 2322 The converted value 2323 """ 2324 if type_name == "NoneType" or value is None: 2325 return None 2326 elif type_name == "bool": 2327 if isinstance(value, str): 2328 return value.lower() in ("true", "1", "yes") 2329 return bool(value) 2330 elif type_name == "int": 2331 return int(value) 2332 elif type_name == "float": 2333 return float(value) 2334 elif type_name == "str": 2335 return str(value) 2336 elif type_name == "list": 2337 if not isinstance(value, list): 2338 return [value] 2339 return value 2340 elif type_name == "dict": 2341 if not isinstance(value, dict): 2342 return {} 2343 return value 2344 elif type_name == "type": 2345 # Handle type objects - convert string representation back to type 2346 if isinstance(value, str): 2347 # Try to evaluate the type string (e.g., "<class 'torch.nn.Linear'>") 2348 # Extract the class path from the string 2349 if value.startswith("<class '") and value.endswith("'>"): 2350 class_path = value[ 2351 8:-2 2352 ] # Extract 'torch.nn.Linear' from "<class 'torch.nn.Linear'>" 2353 parts = class_path.split(".") 2354 # Try to import and get the type 2355 try: 2356 module_name = ".".join(parts[:-1]) 2357 class_name = parts[-1] 2358 module = __import__(module_name, fromlist=[class_name]) 2359 return getattr(module, class_name) 2360 except Exception as e: 2361 print( 2362 f"Warning: Could not convert type string '{value}' to actual type: {e}" 2363 ) 2364 return value 2365 return value 2366 return value 2367 elif type_name == "dtype": 2368 # Handle torch dtype objects 2369 if isinstance(value, str): 2370 # Convert string like "torch.float32" to actual dtype 2371 import torch 2372 2373 try: 2374 # Try to get the dtype from torch module 2375 if value.startswith("torch."): 2376 dtype_name = value.split(".")[ 2377 1 2378 ] # Get 'float32' from 'torch.float32' 2379 return getattr(torch, dtype_name) 2380 else: 2381 return getattr(torch, value) 2382 except Exception as e: 2383 print( 2384 f"Warning: Could not convert dtype string '{value}' to actual dtype: {e}" 2385 ) 2386 return value 2387 return value 2388 elif type_name == "device": 2389 # Handle torch device objects 2390 if isinstance(value, str): 2391 # Convert string like "cuda" or "cpu" to torch.device 2392 import torch 2393 2394 try: 2395 return torch.device(value) 2396 except Exception as e: 2397 print( 2398 f"Warning: Could not convert device string '{value}' to actual device: {e}" 2399 ) 2400 return value 2401 return value 2402 elif type_name == "builtin_function_or_method": 2403 # Handle torch functions like torch.sigmoid, torch.relu, etc. 2404 if isinstance(value, str): 2405 # Parse string like "<built-in method sigmoid of type object at 0x...>" 2406 # to extract the function name 2407 import torch 2408 2409 try: 2410 if "<built-in method " in value and " of type object" in value: 2411 # Extract function name between '<built-in method ' and ' of type object' 2412 start = value.find("<built-in method ") + len("<built-in method ") 2413 end = value.find(" of type object") 2414 func_name = value[start:end] 2415 # Try to get the function from torch module 2416 if hasattr(torch, func_name): 2417 return getattr(torch, func_name) 2418 else: 2419 print(f"Warning: torch.{func_name} not found") 2420 return value 2421 else: 2422 return value 2423 except Exception as e: 2424 print( 2425 f"Warning: Could not convert builtin function string '{value}': {e}" 2426 ) 2427 return value 2428 return value 2429 else: 2430 # Unknown type - error and debug 2431 print(f"ERROR: Unknown type '{type_name}' for value: {value}") 2432 print(f"Type of value is: {type(value).__name__}") 2433 pdb.set_trace() 2434 return value 2435 2436 2437def convert_to_type_array(value, element_type): 2438 """Convert an array's elements to the specified type. 2439 2440 Parameters 2441 ---------- 2442 value : list or tuple 2443 The array to convert 2444 element_type : str or None 2445 The target type name for elements, None if array was empty 2446 2447 Returns 2448 ------- 2449 list 2450 The array with converted elements 2451 """ 2452 if not isinstance(value, (list, tuple)): 2453 return value 2454 # If element_type is None (empty array), no conversion needed 2455 if element_type is None: 2456 return list(value) if isinstance(value, tuple) else value 2457 return [convert_to_type(item, element_type) for item in value] 2458 2459 2460def set_gpa_config(config): 2461 """Set GPA.pc configuration by calling all set_* methods. 2462 2463 This is the reverse of extract_gpa_config(). It takes a configuration 2464 dictionary and calls the corresponding set_* methods on GPA.pc. 2465 Uses type metadata to ensure values are converted to the correct type. 2466 2467 Parameters 2468 ---------- 2469 config : dict 2470 Dictionary with configuration values (keys without 'set_' prefix) 2471 and optional '_types' metadata 2472 2473 Examples 2474 -------- 2475 >>> config = {'verbose': True, 'device': 'cuda'} 2476 >>> set_gpa_config(config) 2477 # Calls GPA.pc.set_verbose(True), GPA.pc.set_device('cuda'), etc. 2478 2479 Returns 2480 ------- 2481 Count of parameters that were set 2482 """ 2483 set_count = 0 2484 skip_count = 0 2485 2486 # Extract type information 2487 config_types = config.get("_types", {}) 2488 2489 for key, value in config.items(): 2490 # Skip the types metadata 2491 if key == "_types": 2492 continue 2493 2494 # Construct the set method name 2495 set_method_name = f"set_{key}" 2496 2497 # Check if the set method exists 2498 if hasattr(GPA.pc, set_method_name): 2499 try: 2500 method = getattr(GPA.pc, set_method_name) 2501 if callable(method): 2502 # Convert value to correct type if we have type info 2503 if key in config_types: 2504 type_info = config_types[key] 2505 if type_info.get("is_array", False): 2506 # Convert array elements to correct type 2507 element_type = type_info.get("element_type", "str") 2508 value = convert_to_type_array(value, element_type) 2509 else: 2510 # Convert single value to correct type 2511 value_type = type_info.get("type", "str") 2512 value = convert_to_type(value, value_type) 2513 2514 method(value) 2515 set_count += 1 2516 if GPA.pc.get_verbose(): 2517 print(f"Set {key} = {value}") 2518 except Exception as e: 2519 skip_count += 1 2520 if GPA.pc.get_verbose(): 2521 print(f"Failed to set {key}: {e}") 2522 else: 2523 skip_count += 1 2524 if GPA.pc.get_verbose(): 2525 print(f"No setter found for {key} (looking for {set_method_name})") 2526 2527 if GPA.pc.get_verbose(): 2528 print(f"Applied {set_count} PAI configuration settings ({skip_count} skipped)") 2529 2530 return set_count 2531 2532 2533try: 2534 from huggingface_hub import PyTorchModelHubMixin, hf_hub_download, HfApi 2535 2536 def upload_to_huggingface( 2537 model, 2538 repo_id, 2539 license="apache-2.0", 2540 pipeline_tag=None, 2541 repo_url=None, 2542 tags=None, 2543 include_pai_config=True, 2544 **kwargs, 2545 ): 2546 """Upload a model to HuggingFace Hub. 2547 2548 Uploads model weights and PAI configuration to HuggingFace Hub. 2549 The configuration is saved in config.json and can be restored when loading. 2550 2551 Parameters 2552 ---------- 2553 model : nn.Module 2554 The model to upload 2555 repo_id : str 2556 Repository ID (format: "username/model-name") 2557 license : str, optional 2558 License for the model card, by default "apache-2.0" 2559 pipeline_tag : str, optional 2560 Pipeline tag for the model (e.g., "text-classification", "image-classification") 2561 repo_url : str, optional 2562 URL to the model's repository/documentation 2563 tags : list, optional 2564 List of tags for the model card 2565 include_pai_config : bool, optional 2566 Whether to include all GPA.pc configuration in the model config, by default True 2567 **kwargs 2568 Additional arguments passed to HfApi (token, private, etc.) 2569 2570 Returns 2571 ------- 2572 str 2573 URL of the uploaded model 2574 2575 Examples 2576 -------- 2577 >>> url = upload_to_huggingface( 2578 ... model, 2579 ... "username/my-model", 2580 ... license="mit", 2581 ... pipeline_tag="image-classification", 2582 ... tags=["pytorch", "vision"] 2583 ... ) 2584 """ 2585 try: 2586 from huggingface_hub import HfApi 2587 except ImportError: 2588 raise ImportError( 2589 "huggingface_hub is required. Install it with: pip install huggingface_hub" 2590 ) 2591 2592 import tempfile 2593 import os 2594 2595 # Prepare model same way as save_pai_net does 2596 model = prepare_final_model(model) 2597 2598 # Calculate parameter count 2599 param_count = count_params(model) 2600 2601 # Format parameter count for tags (e.g., "11m" for 11 million) 2602 if param_count >= 1e9: 2603 param_tag = f"{param_count/1e9:.0f}b" 2604 elif param_count >= 1e6: 2605 param_tag = f"{param_count/1e6:.0f}m" 2606 elif param_count >= 1e3: 2607 param_tag = f"{param_count/1e3:.0f}k" 2608 else: 2609 param_tag = str(param_count) 2610 2611 # Create a temporary directory for files 2612 with tempfile.TemporaryDirectory() as tmpdir: 2613 # Save model weights 2614 model_path = os.path.join(tmpdir, "model.safetensors") 2615 save_file(model.state_dict(), model_path) 2616 2617 # Create config with PAI configuration 2618 config = {} 2619 if include_pai_config: 2620 pai_config = extract_gpa_config() 2621 config["pai_config"] = pai_config 2622 if GPA.pc.get_verbose(): 2623 print(f"Extracted {len(pai_config)} PAI configuration parameters") 2624 2625 # Add parameter count at top level 2626 config["num_parameters"] = param_count 2627 2628 # Add metadata 2629 if license: 2630 config["license"] = license 2631 if pipeline_tag: 2632 config["pipeline_tag"] = pipeline_tag 2633 if repo_url: 2634 config["repo_url"] = repo_url 2635 2636 # Add tags with parameter count 2637 if tags is None: 2638 tags = [] 2639 elif not isinstance(tags, list): 2640 tags = [tags] 2641 else: 2642 tags = tags.copy() # Don't modify the original list 2643 2644 # Add perforated-ai tag if not present 2645 if "perforated-ai" not in tags: 2646 tags.insert(0, "perforated-ai") 2647 2648 # Add parameter count tag if not present 2649 if param_tag not in tags: 2650 tags.append(param_tag) 2651 2652 config["tags"] = tags 2653 2654 # Save config.json 2655 config_path = os.path.join(tmpdir, "config.json") 2656 with open(config_path, "w") as f: 2657 json.dump(config, f, indent=2) 2658 2659 # Upload to HuggingFace 2660 api = HfApi() 2661 2662 # Extract token from kwargs if present 2663 token = kwargs.pop("token", None) 2664 private = kwargs.pop("private", None) 2665 2666 # Create repo if it doesn't exist 2667 try: 2668 api.create_repo( 2669 repo_id=repo_id, token=token, private=private, exist_ok=True 2670 ) 2671 except Exception as e: 2672 print(f"Repo may already exist: {e}") 2673 2674 # Upload folder 2675 api.upload_folder( 2676 folder_path=tmpdir, repo_id=repo_id, token=token, **kwargs 2677 ) 2678 2679 print(f"Model uploaded to: https://huggingface.co/{repo_id}") 2680 if include_pai_config: 2681 print(f"PAI configuration saved in config.json") 2682 print(f"To reload, use: model = from_hf_pretrained(model, '{repo_id}')") 2683 2684 return f"https://huggingface.co/{repo_id}" 2685 2686 def from_hf_pretrained(net, repo_id, force_download=False): 2687 """Load a PerforatedAI model from HuggingFace Hub using PyTorchModelHubMixin. 2688 2689 Args: 2690 net: The base model architecture (will be converted to PAI format) 2691 repo_id: HuggingFace Hub repository ID (e.g., "username/model-name") 2692 force_download: If True, always download the latest version, bypassing cache (default: False) 2693 2694 Returns: 2695 net: The loaded model with PAI modules initialized 2696 2697 Parameters 2698 ---------- 2699 net : PyTorch Model. 2700 repo_id : Name of HuggingFace repository. 2701 force_download : Force an update even if local file exists. 2702 2703 Returns 2704 ------- 2705 Loaded Model 2706 """ 2707 2708 # Wrap in a class that inherits from PyTorchModelHubMixin 2709 class PAIHFModel(net.__class__, PyTorchModelHubMixin): 2710 def __init__(self, *args, **kwargs): 2711 """Initialize the temporary HuggingFace-compatible wrapper.""" 2712 super().__init__(*args, **kwargs) 2713 2714 # Create an instance that can use from_pretrained 2715 wrapped_net = PAIHFModel.__new__(PAIHFModel) 2716 wrapped_net.__dict__ = net.__dict__ 2717 wrapped_net.__class__ = PAIHFModel 2718 2719 # Download config.json to restore PAI configuration 2720 try: 2721 config_path = hf_hub_download(repo_id=repo_id, filename="config.json", force_download=force_download) 2722 with open(config_path, "r") as f: 2723 config = json.load(f) 2724 if "pai_config" in config: 2725 # print(f"Restoring PAI configuration from HuggingFace") 2726 set_gpa_config(config["pai_config"]) 2727 else: 2728 print("Warning: No pai_config found in config.json") 2729 except Exception as e: 2730 print(f"Warning: Could not load PAI config from HuggingFace: {e}") 2731 2732 # Download model files from HuggingFace 2733 model_path = hf_hub_download(repo_id=repo_id, filename="model.safetensors", force_download=force_download) 2734 state_dict = load_file(model_path) 2735 wrapped_net = NPA.convert_network(wrapped_net) 2736 wrapped_net = NPA.load_pai_model_from_dict(wrapped_net, state_dict) 2737 return wrapped_net 2738 2739except: 2740 2741 def upload_to_huggingface(*args, **kwargs): 2742 """Raise an informative error when HuggingFace dependencies are missing. 2743 2744 Parameters 2745 ---------- 2746 *args : tuple[Any, ...] 2747 Positional arguments accepted for API compatibility. 2748 **kwargs : dict[str, Any] 2749 Keyword arguments accepted for API compatibility. 2750 2751 Returns 2752 ------- 2753 None 2754 Always raises ``ImportError``. 2755 """ 2756 raise ImportError( 2757 "huggingface_hub is required for upload_to_huggingface. " 2758 "Install it with: pip install huggingface_hub" 2759 ) 2760 2761 def from_hf_pretrained(*args, **kwargs): 2762 """Raise an informative error when HuggingFace dependencies are missing. 2763 2764 Parameters 2765 ---------- 2766 *args : tuple[Any, ...] 2767 Positional arguments accepted for API compatibility. 2768 **kwargs : dict[str, Any] 2769 Keyword arguments accepted for API compatibility. 2770 2771 Returns 2772 ------- 2773 None 2774 Always raises ``ImportError``. 2775 """ 2776 raise ImportError( 2777 "huggingface_hub is required for from_hf_pretrained. " 2778 "Install it with: pip install huggingface_hub" 2779 )
47def perforate_model( 48 model, 49 doing_pai=True, 50 save_name="", 51 making_graphs=True, 52 maximizing_score=True, 53 num_classes=10000000000, 54 values_per_train_epoch=-1, 55 values_per_val_epoch=-1, 56 zooming_graph=True, 57): 58 """Main function to initialize the network to add dendrites 59 60 This kicks off the entire Perforated AI process to add 61 the scaffolding to the network to be able to add dendrites 62 63 Parameters 64 ---------- 65 model : nn.Module 66 The neural network model to initialize. 67 doing_pai : bool, optional 68 Whether to actually add dendrites, by default True 69 save_name : str, optional 70 The name to save the model under, by default "PAI" 71 making_graphs : bool, optional 72 Whether to create graphs during training, by default True 73 maximizing_score : bool, optional 74 Whether to maximize the score during training, by default True 75 setting to false is for when the score is a loss to be minimized 76 num_classes : int, optional 77 The number of output classes, unused in current version 78 values_per_train_epoch : int, optional 79 The number of values to look back for graphing 80 during training, by default -1 (all values). 81 values_per_val_epoch : int, optional 82 The number of values to look back for graphing 83 during validation, by default -1 (all values). 84 zooming_graph : bool, optional 85 Whether to enable zooming on the graphs, by default True 86 87 Returns 88 ------- 89 model : nn.Module 90 The modified model with dendrite scaffolding added if doing_pai is True 91 92 """ 93 94 if save_name == "": 95 if GPA.pc.get_save_name() == "": 96 save_name = "PAI" 97 else: 98 save_name = GPA.pc.get_save_name() 99 100 if "/" in save_name: 101 print( 102 f"Warning: save_name '{save_name}' contains '/'. Relative paths are not implemented yet." 103 ) 104 sys.exit(1) 105 106 sanitized_save_name = "".join( 107 ch for ch in save_name if ch.isalnum() or ch in ("_", "-", ".") 108 ) 109 if sanitized_save_name != save_name: 110 print( 111 f"Warning: save_name '{save_name}' contained spaces or special characters. " 112 f"Using '{sanitized_save_name}' instead." 113 ) 114 save_name = sanitized_save_name 115 116 if save_name == "": 117 print("Warning: save_name became empty after sanitization. Using 'PAI'.") 118 save_name = "PAI" 119 120 121 GPA.pai_tracker = TPA.PAINeuronModuleTracker( 122 doing_pai=doing_pai, save_name=save_name 123 ) 124 GPA.pc.set_save_name(save_name) 125 if _dashboard_emitter is not None: 126 _dashboard_emitter.emit_run_start(GPA.pc, save_name) 127 model = GPA.pai_tracker.initialize( 128 model, 129 doing_pai=doing_pai, 130 save_name=save_name, 131 making_graphs=making_graphs, 132 maximizing_score=maximizing_score, 133 num_classes=num_classes, 134 values_per_train_epoch=-values_per_train_epoch, 135 values_per_val_epoch=values_per_val_epoch, 136 zooming_graph=zooming_graph, 137 ) 138 139 # Save config after perforation 140 if not GPA.pc.get_testing_dendrite_capacity(): 141 import os 142 GPA.pc.save_config(os.path.join(os.getcwd(), save_name, f"{save_name}_config.json")) 143 144 return model
Main function to initialize the network to add dendrites
This kicks off the entire Perforated AI process to add the scaffolding to the network to be able to add dendrites
Parameters
- model (nn.Module): The neural network model to initialize.
- doing_pai (bool, optional): Whether to actually add dendrites, by default True
- save_name (str, optional): The name to save the model under, by default "PAI"
- making_graphs (bool, optional): Whether to create graphs during training, by default True
- maximizing_score (bool, optional): Whether to maximize the score during training, by default True setting to false is for when the score is a loss to be minimized
- num_classes (int, optional): The number of output classes, unused in current version
- values_per_train_epoch (int, optional): The number of values to look back for graphing during training, by default -1 (all values).
- values_per_val_epoch (int, optional): The number of values to look back for graphing during validation, by default -1 (all values).
- zooming_graph (bool, optional): Whether to enable zooming on the graphs, by default True
Returns
- model (nn.Module): The modified model with dendrite scaffolding added if doing_pai is True
147def get_pai_modules(net, depth, seen_ids=None): 148 """Get a list of all neuron modules 149 150 Parameters 151 ---------- 152 net : nn.Module 153 The module to search. 154 depth : int 155 The current depth in the recursion. 156 157 Returns 158 ------- 159 list 160 A list of all PAI neuron modules found in the network. 161 162 """ 163 if seen_ids is None: 164 seen_ids = set() 165 all_members = net.__dir__() 166 this_list = [] 167 if issubclass(type(net), nn.Sequential) or issubclass(type(net), nn.ModuleList): 168 for submodule_id, layer in net.named_children(): 169 # If there is a self pointer ignore it 170 if net.get_submodule(submodule_id) is net: 171 continue 172 if type(net.get_submodule(submodule_id)) is PA.PAINeuronModule: 173 module = net.get_submodule(submodule_id) 174 if id(module) in seen_ids: 175 continue 176 seen_ids.add(id(module)) 177 this_list = this_list + [module] 178 else: 179 this_list = this_list + get_pai_modules( 180 net.get_submodule(submodule_id), depth + 1, seen_ids 181 ) 182 else: 183 for member in all_members: 184 if isinstance(getattr(type(net), member, None), property): 185 continue 186 # if the getter fails or it is a self pointer ignore it 187 try: 188 if getattr(net, member, None) is net: 189 continue 190 except: 191 continue 192 if type(getattr(net, member, None)) is PA.PAINeuronModule: 193 module = getattr(net, member) 194 if id(module) in seen_ids: 195 continue 196 seen_ids.add(id(module)) 197 this_list = this_list + [module] 198 elif ( 199 issubclass(type(getattr(net, member, None)), nn.Module) 200 or issubclass(type(getattr(net, member, None)), nn.Sequential) 201 or issubclass(type(getattr(net, member, None)), nn.ModuleList) 202 ): 203 this_list = this_list + get_pai_modules( 204 getattr(net, member), depth + 1, seen_ids 205 ) 206 207 return this_list
Get a list of all neuron modules
Parameters
- net (nn.Module): The module to search.
- depth (int): The current depth in the recursion.
Returns
- list: A list of all PAI neuron modules found in the network.
210def get_tracked_modules(net, depth, seen_ids=None): 211 """Get a list of all tracked modules 212 213 Parameters 214 ---------- 215 net : nn.Module 216 The module to search. 217 depth : int 218 The current depth in the recursion. 219 220 Returns 221 ------- 222 list 223 A list of all tracked modules found in the network. 224 225 """ 226 if seen_ids is None: 227 seen_ids = set() 228 all_members = net.__dir__() 229 this_list = [] 230 if issubclass(type(net), nn.Sequential) or issubclass(type(net), nn.ModuleList): 231 for submodule_id, layer in net.named_children(): 232 if net.get_submodule(submodule_id) is net: 233 continue 234 if type(net.get_submodule(submodule_id)) is PA.TrackedNeuronModule: 235 module = net.get_submodule(submodule_id) 236 if id(module) in seen_ids: 237 continue 238 seen_ids.add(id(module)) 239 this_list = this_list + [module] 240 else: 241 this_list = this_list + get_tracked_modules( 242 net.get_submodule(submodule_id), depth + 1, seen_ids 243 ) 244 else: 245 for member in all_members: 246 if isinstance(getattr(type(net), member, None), property): 247 continue 248 # if the getter fails or it is a self pointer ignore it 249 try: 250 if getattr(net, member, None) is net: 251 continue 252 except: 253 continue 254 if type(getattr(net, member, None)) is PA.TrackedNeuronModule: 255 module = getattr(net, member) 256 if id(module) in seen_ids: 257 continue 258 seen_ids.add(id(module)) 259 this_list = this_list + [module] 260 elif issubclass(type(getattr(net, member, None)), nn.Module): 261 this_list = this_list + get_tracked_modules( 262 getattr(net, member), depth + 1, seen_ids 263 ) 264 return this_list
Get a list of all tracked modules
Parameters
- net (nn.Module): The module to search.
- depth (int): The current depth in the recursion.
Returns
- list: A list of all tracked modules found in the network.
267def get_pai_module_params(net, depth, seen_ids=None): 268 """Get a list of all neuron module parameters 269 270 Parameters 271 ---------- 272 net : nn.Module 273 The module to search. 274 depth : int 275 The current depth in the recursion. 276 277 Returns 278 ------- 279 list 280 A list of all parameters of neuron modules found in this module. 281 282 """ 283 284 if seen_ids is None: 285 seen_ids = set() 286 all_members = net.__dir__() 287 this_list = [] 288 if issubclass(type(net), nn.Sequential) or issubclass(type(net), nn.ModuleList): 289 for submodule_id, layer in net.named_children(): 290 if isinstance(net.get_submodule(submodule_id), PA.PAINeuronModule): # 291 module = net.get_submodule(submodule_id) 292 if id(module) in seen_ids: 293 continue 294 seen_ids.add(id(module)) 295 for param in module.parameters(): 296 if param.requires_grad: 297 this_list = this_list + [param] 298 else: 299 this_list = this_list + get_pai_module_params( 300 net.get_submodule(submodule_id), depth + 1, seen_ids 301 ) 302 else: 303 for member in all_members: 304 if isinstance(getattr(type(net), member, None), property): 305 continue 306 if getattr(net, member, None) == net: 307 continue 308 if isinstance(getattr(net, member, None), PA.PAINeuronModule): 309 module = getattr(net, member) 310 if id(module) in seen_ids: 311 continue 312 seen_ids.add(id(module)) 313 for param in module.parameters(): 314 if param.requires_grad: 315 this_list = this_list + [param] 316 elif issubclass(type(getattr(net, member, None)), nn.Module): 317 this_list = this_list + get_pai_module_params( 318 getattr(net, member), depth + 1, seen_ids 319 ) 320 return this_list
Get a list of all neuron module parameters
Parameters
- net (nn.Module): The module to search.
- depth (int): The current depth in the recursion.
Returns
- list: A list of all parameters of neuron modules found in this module.
323def get_pai_network_params(net): 324 """Get a list of all neuron module parameters 325 326 Parameters 327 ---------- 328 net : nn.Module 329 The full model to search. 330 331 Returns 332 ------- 333 list 334 A list of all parameters of neuron modules found in the network. 335 336 """ 337 param_list = get_pai_module_params(net, 0) 338 return param_list
Get a list of all neuron module parameters
Parameters
- net (nn.Module): The full model to search.
Returns
- list: A list of all parameters of neuron modules found in the network.
341def replace_predefined_modules(start_module): 342 """Replace a module with the module from globals list 343 344 Parameters 345 ---------- 346 start_module : nn.Module 347 The module to replace. 348 349 Returns 350 ------- 351 nn.Module 352 The replaced module. 353 354 """ 355 index = GPA.pc.get_modules_to_replace().index(type(start_module)) 356 return GPA.pc.get_replacement_modules()[index](start_module)
Replace a module with the module from globals list
Parameters
- start_module (nn.Module): The module to replace.
Returns
- nn.Module: The replaced module.
359def scan_module_aliases(net): 360 """Find alias module paths that point to already-seen module instances. 361 362 Parameters 363 ---------- 364 net : Any PyTorch Module. 365 366 Returns 367 ------- 368 dict[str, str] 369 Mapping of alias module paths to their canonical module paths. 370 """ 371 canonical = {} 372 aliases = {} 373 for name, module in net.named_modules(remove_duplicate=False): 374 if name == "": 375 continue 376 sub_name = "." + name 377 module_id = id(module) 378 if module_id in canonical: 379 aliases[sub_name] = canonical[module_id] 380 else: 381 canonical[module_id] = sub_name 382 return aliases
Find alias module paths that point to already-seen module instances.
Parameters
- net (Any PyTorch Module.):
Returns
- dict[str, str]: Mapping of alias module paths to their canonical module paths.
385def convert_module( 386 net, 387 depth, 388 name_so_far, 389 converted_list, 390 converted_names_list, 391 neuron_module_class, 392 tracked_module_class, 393): 394 """Recursive function to do all conversion of modules to wrappers of modules 395 396 This is the function that goes through all of the module lists from 397 the globals file and does all the conversion and replacements to 398 setup the dendrite scaffolding as instructed. 399 400 Parameters 401 ---------- 402 net : nn.Module 403 The module to convert. 404 depth : int 405 The current depth in the recursion. 406 name_so_far : str 407 The name of the module so far in the recursion. 408 converted_list : list 409 A list of already converted module ids to avoid infinite loops. 410 converted_names_list : list 411 A corresponding list to help debug duplicate conversions 412 413 Returns 414 ------- 415 nn.Module 416 The converted module. 417 418 """ 419 if GPA.pc.get_verbose(): 420 print("calling convert on %s depth %d" % (net, depth)) 421 print( 422 "calling convert on %s: %s, depth %d" 423 % (name_so_far, type(net).__name__, depth) 424 ) 425 if isinstance(net, neuron_module_class) or ( 426 (tracked_module_class is not None) and isinstance(net, tracked_module_class) 427 ): 428 if GPA.pc.get_verbose(): 429 print( 430 "This is only being called because something in your model " 431 "is pointed to twice by two different variables. Highest " 432 "thing on the list is one of the duplicates" 433 ) 434 return net 435 if depth == 0 and name_so_far == "": 436 aliases = scan_module_aliases(net) 437 existing_not_save = set(GPA.pc.get_module_names_to_not_save()) 438 aliases_to_skip = [ 439 alias for alias in aliases.keys() if alias not in existing_not_save 440 ] 441 if aliases_to_skip: 442 GPA.pc.append_module_names_to_not_save(aliases_to_skip) 443 print( 444 "Auto-detected duplicate module aliases via named_modules; " 445 "keeping first-seen paths and skipping:" 446 ) 447 for alias in aliases_to_skip: 448 print(" - %s (keeps %s)" % (alias, aliases[alias])) 449 all_members = net.__dir__() 450 if GPA.pc.get_extra_verbose(): 451 print("all members:") 452 for member in all_members: 453 print(" - %s" % member) 454 if issubclass(type(net), nn.Sequential) or issubclass(type(net), nn.ModuleList): 455 for submodule_id, layer in net.named_children(): 456 sub_name = name_so_far + "." + str(submodule_id) 457 if sub_name in GPA.pc.get_module_ids_to_track(): 458 if GPA.pc.get_verbose(): 459 print("Seq ID is in track IDs: %s" % sub_name) 460 if tracked_module_class is None: 461 continue 462 setattr( 463 net, 464 submodule_id, 465 tracked_module_class(net.get_submodule(submodule_id), sub_name), 466 ) 467 continue 468 if sub_name in GPA.pc.get_module_ids_to_perforate(): 469 if GPA.pc.get_verbose(): 470 print("Seq ID is in convert IDs: %s" % sub_name) 471 setattr( 472 net, 473 submodule_id, 474 neuron_module_class(net.get_submodule(submodule_id), sub_name), 475 ) 476 continue 477 if type(net.get_submodule(submodule_id)) in GPA.pc.get_modules_to_replace(): 478 if GPA.pc.get_verbose(): 479 print( 480 "Seq sub is in replacement module so replacing: %s" % sub_name 481 ) 482 setattr( 483 net, 484 submodule_id, 485 replace_predefined_modules(net.get_submodule(submodule_id)), 486 ) 487 if ( 488 type(net.get_submodule(submodule_id)) in GPA.pc.get_modules_to_track() 489 ) or ( 490 type(net.get_submodule(submodule_id)).__name__ 491 in GPA.pc.get_module_names_to_track() 492 ): 493 if GPA.pc.get_verbose(): 494 print( 495 "Seq sub is in tracking list so initiating tracked for: %s" 496 % sub_name 497 ) 498 if tracked_module_class is None: 499 continue 500 setattr( 501 net, 502 submodule_id, 503 tracked_module_class(net.get_submodule(submodule_id), sub_name), 504 ) 505 elif ( 506 type(net.get_submodule(submodule_id)) 507 in GPA.pc.get_modules_to_perforate() 508 or type(net.get_submodule(submodule_id)).__name__ 509 in GPA.pc.get_module_names_to_perforate() 510 ): 511 if GPA.pc.get_verbose(): 512 print( 513 "Seq sub is in conversion list so initing PAI for: " 514 "%s" % sub_name 515 ) 516 if ( 517 issubclass( 518 type(net.get_submodule(submodule_id)), 519 torch.nn.modules.batchnorm._BatchNorm, 520 ) 521 or issubclass( 522 type(net.get_submodule(submodule_id)), 523 torch.nn.modules.instancenorm._InstanceNorm, 524 ) 525 or issubclass( 526 type(net.get_submodule(submodule_id)), 527 torch.nn.modules.normalization.LayerNorm, 528 ) 529 ): 530 print( 531 "You have an unwrapped normalization layer, this " 532 "is not recommended: " + name_so_far 533 ) 534 pdb.set_trace() 535 setattr( 536 net, 537 submodule_id, 538 neuron_module_class(net.get_submodule(submodule_id), sub_name), 539 ) 540 else: 541 if net != net.get_submodule(submodule_id): 542 converted_list += [id(net.get_submodule(submodule_id))] 543 converted_names_list += [sub_name] 544 if GPA.pc.get_verbose(): 545 print( 546 "sub is module but in no lists so going deeper: %s" 547 % sub_name 548 ) 549 550 setattr( 551 net, 552 submodule_id, 553 convert_module( 554 net.get_submodule(submodule_id), 555 depth + 1, 556 sub_name, 557 converted_list, 558 converted_names_list, 559 neuron_module_class, 560 tracked_module_class, 561 ), 562 ) 563 # else: 564 # print('%s is a self pointer so skipping' % (name_so_far + '[' + str(submodule_id) + ']')) 565 elif type(net) in GPA.pc.get_modules_to_track(): 566 # print('skipping type for returning from call to: %s' % (name_so_far)) 567 return net 568 else: 569 for member in all_members: 570 if isinstance(getattr(type(net), member, None), property): 571 continue 572 # Immediately check if able to get the member, if not skip it 573 try: 574 getattr(net, member, None) 575 except: 576 continue 577 sub_name = name_so_far + "." + member 578 member_obj = getattr(net, member, None) 579 580 if isinstance(member_obj, (torch.nn.Parameter, torch.nn.parameter.Parameter)): 581 if sub_name in GPA.pc.get_parameter_ids_to_track(): 582 if GPA.pc.get_verbose(): 583 print("tracking parameter by ID: %s" % sub_name) 584 member_obj.parameter_type = "neuron" 585 member_obj.wrapped = True 586 continue 587 588 # Track module object ids once at this level so duplicate aliases are 589 # caught consistently (including direct children of the root module). 590 if isinstance(member_obj, nn.Module): 591 if id(member_obj) in converted_list: 592 original_sub_name = converted_names_list[ 593 converted_list.index(id(member_obj)) 594 ] 595 print( 596 "The following module has a duplicate pointer within " 597 "your model: %s" % sub_name 598 ) 599 print("Keeping first pointer: %s" % original_sub_name) 600 print("Skipping duplicate pointer: %s" % sub_name) 601 print( 602 "If you prefer to keep %s and skip %s, add %s to module_names_to_not_save before convert." 603 % (sub_name, original_sub_name, original_sub_name) 604 ) 605 GPA.pc.append_module_names_to_not_save([sub_name]) 606 continue 607 converted_list += [id(member_obj)] 608 converted_names_list += [sub_name] 609 if sub_name in GPA.pc.get_module_ids_to_track(): 610 if GPA.pc.get_verbose(): 611 print("Seq ID is in track IDs: %s" % sub_name) 612 if tracked_module_class is None: 613 continue 614 setattr( 615 net, member, tracked_module_class(getattr(net, member), sub_name) 616 ) 617 continue 618 if sub_name in GPA.pc.get_module_ids_to_perforate(): 619 if GPA.pc.get_verbose(): 620 print("Seq ID is in convert IDs: %s" % sub_name) 621 setattr( 622 net, member, neuron_module_class(getattr(net, member), sub_name) 623 ) 624 continue 625 if id(getattr(net, member, None)) == id(net): 626 if GPA.pc.get_verbose(): 627 print("member sub is a self pointer: %s" % sub_name) 628 continue 629 if sub_name in GPA.pc.get_module_names_to_not_save(): 630 if GPA.pc.get_verbose(): 631 print("Skipping %s during convert" % sub_name) 632 else: 633 if sub_name == ".base_model": 634 print( 635 "By default skipping base_model. See " 636 '"Safetensors Errors" section of ' 637 "customization.md to include it." 638 ) 639 continue 640 if type(getattr(net, member, None)) in GPA.pc.get_modules_to_replace(): 641 if GPA.pc.get_verbose(): 642 print("sub is in replacement module so replacing: %s" % sub_name) 643 setattr( 644 net, member, replace_predefined_modules(getattr(net, member, None)) 645 ) 646 if ( 647 type(getattr(net, member, None)) in GPA.pc.get_modules_to_track() 648 or type(getattr(net, member, None)).__name__ 649 in GPA.pc.get_module_names_to_track() 650 or sub_name in GPA.pc.get_module_ids_to_track() 651 ): 652 if GPA.pc.get_verbose(): 653 print( 654 "sub is in tracking list so initiating tracked for: %s" 655 % sub_name 656 ) 657 if tracked_module_class is None: 658 continue 659 setattr( 660 net, member, tracked_module_class(getattr(net, member), sub_name) 661 ) 662 elif ( 663 type(getattr(net, member, None)) in GPA.pc.get_modules_to_perforate() 664 or type(getattr(net, member, None)).__name__ 665 in GPA.pc.get_module_names_to_perforate() 666 or (sub_name in GPA.pc.get_module_ids_to_perforate()) 667 ): 668 if GPA.pc.get_verbose(): 669 print( 670 "sub is in conversion list so initiating PAI for: %s" % sub_name 671 ) 672 setattr( 673 net, 674 member, 675 neuron_module_class(getattr(net, member), sub_name), 676 ) 677 elif ( 678 issubclass(type(getattr(net, member, None)), nn.Module) 679 or issubclass(type(getattr(net, member, None)), nn.Sequential) 680 or issubclass(type(getattr(net, member, None)), nn.ModuleList) 681 ): 682 if net != getattr(net, member): 683 if GPA.pc.get_verbose(): 684 print( 685 "sub is module but in no lists so going deeper: %s" 686 % sub_name 687 ) 688 setattr( 689 net, 690 member, 691 convert_module( 692 getattr(net, member), 693 depth + 1, 694 sub_name, 695 converted_list, 696 converted_names_list, 697 neuron_module_class, 698 tracked_module_class, 699 ), 700 ) 701 if ( 702 issubclass( 703 type(getattr(net, member, None)), 704 torch.nn.modules.batchnorm._BatchNorm, 705 ) 706 or issubclass( 707 type(getattr(net, member, None)), 708 torch.nn.modules.instancenorm._InstanceNorm, 709 ) 710 or issubclass( 711 type(getattr(net, member, None)), 712 torch.nn.modules.normalization.LayerNorm, 713 ) 714 ): 715 if not GPA.pc.get_unwrapped_modules_confirmed(): 716 print( 717 "potentially found a norm Layer that " 718 "is not accounted for, this is not recommended: %s" % (sub_name) 719 ) 720 print( 721 "Set GPA.pc.set_unwrapped_modules_confirmed(True) to skip " 722 "this next time" 723 ) 724 print( 725 "inspect your network to " 726 "see what the module type containing this layer is." 727 ) 728 print("Then do one of the following:") 729 print( 730 " - Add the module type to " 731 "GPA.pc.get_module_names_to_perforate() to wrap it entirely" 732 ) 733 print( 734 " - If the norm layer is part of a sequential wrap " 735 "it and the previous layer in a PAISequential" 736 ) 737 print( 738 " - If you do not want to add dendrites to this " 739 "module add the type to GPA.pc.get_module_names_to_track()" 740 ) 741 pdb.set_trace() 742 else: 743 if GPA.pc.get_verbose(): 744 if member[0] != "_" or GPA.pc.get_extra_verbose() is True: 745 print("not calling convert on %s depth %d" % (member, depth)) 746 if GPA.pc.get_verbose(): 747 print("returning from call to: %s" % (name_so_far)) 748 return net
Recursive function to do all conversion of modules to wrappers of modules
This is the function that goes through all of the module lists from the globals file and does all the conversion and replacements to setup the dendrite scaffolding as instructed.
Parameters
- net (nn.Module): The module to convert.
- depth (int): The current depth in the recursion.
- name_so_far (str): The name of the module so far in the recursion.
- converted_list (list): A list of already converted module ids to avoid infinite loops.
- converted_names_list (list): A corresponding list to help debug duplicate conversions
Returns
- nn.Module: The converted module.
751def convert_network(net, layer_name=""): 752 """Function that calls convert_module and checks results 753 754 Parameters 755 ---------- 756 net : nn.Module 757 The network to convert. 758 layer_name : str, optional 759 The name of the layer if converting a single layer, by default "" 760 761 Returns 762 ------- 763 nn.Module 764 The converted network. 765 766 """ 767 if GPA.pc.get_perforated_backpropagation(): 768 UPB.initialize_pb() 769 MPB.set_main_parameters(net) 770 if type(net) in GPA.pc.get_modules_to_replace(): 771 net = replace_predefined_modules(net) 772 if (type(net) in GPA.pc.get_modules_to_perforate()) or ( 773 type(net).__name__ in GPA.pc.get_module_names_to_perforate() 774 ): 775 if layer_name == "": 776 print( 777 "converting a single layer without a name, add a " 778 "layer_name param to the call" 779 ) 780 sys.exit(-1) 781 net = PA.PAINeuronModule(net, layer_name) 782 else: 783 net = convert_module( 784 net, 0, "", [], [], PA.PAINeuronModule, PA.TrackedNeuronModule 785 ) 786 if GPA.pai_tracker.member_vars["doing_pai"]: 787 missed_ones = [] 788 tracked_ones = [] 789 for name, param in net.named_parameters(): 790 wrapped = "wrapped" in param.__dir__() 791 if wrapped: 792 if GPA.pc.get_verbose(): 793 print("param %s is now wrapped" % (name)) 794 else: 795 tracked = "tracked" in param.__dir__() 796 if tracked: 797 tracked_ones.append(name) 798 else: 799 missed_ones.append(name) 800 if ( 801 len(missed_ones) != 0 or len(tracked_ones) != 0 802 ) and GPA.pc.get_unwrapped_modules_confirmed() is False: 803 print( 804 "\n------------------------------------------------------------------" 805 ) 806 print( 807 "The following params are not wrapped.\n------------------------------------------------------------------" 808 ) 809 for name in tracked_ones: 810 print("." + name) 811 print( 812 "\n------------------------------------------------------------------" 813 ) 814 print( 815 "The following params are not tracked or wrapped.\n------------------------------------------------------------------" 816 ) 817 for name in missed_ones: 818 print("." + name) 819 print( 820 "\n------------------------------------------------------------------" 821 ) 822 print( 823 "Modules that are not wrapped will not have Dendrites to optimize them" 824 ) 825 print( 826 "Modules modules that are not tracked can cause errors and is NOT recommended" 827 ) 828 print( 829 "Any modules in the second list should be added to module_names_to_track" 830 ) 831 832 print( 833 "Set GPA.pc.set_unwrapped_modules_confirmed(True) to skip this next time" 834 ) 835 print( 836 "Inspect your network and see what the module types of these values are to add them to PGB.module_names_to_perforate" 837 ) 838 # If did miss some then set trace to debug 839 if len(missed_ones) != 0: 840 print( 841 "------------------------------------------------------------------\nType 'c' + enter to continue the run to confirm you do not want them to be refined" 842 ) 843 844 pdb.set_trace() 845 print("confirmed") 846 net.register_buffer("tracker_string", torch.tensor([], dtype=torch.uint8)) 847 return net
Function that calls convert_module and checks results
Parameters
- net (nn.Module): The network to convert.
- layer_name (str, optional): The name of the layer if converting a single layer, by default ""
Returns
- nn.Module: The converted network.
850def string_to_tensor(string): 851 """Helper function to convert a layer_tracker into a string 852 853 This is required for safetensors saving 854 855 Parameters 856 ---------- 857 string : str 858 The string to convert. 859 860 Returns 861 ------- 862 torch.Tensor 863 The converted tensor. 864 865 """ 866 ords = list(map(ord, string)) 867 ords = torch.tensor(ords, dtype=torch.uint8) 868 return ords
Helper function to convert a layer_tracker into a string
This is required for safetensors saving
Parameters
- string (str): The string to convert.
Returns
- torch.Tensor: The converted tensor.
871def string_from_tensor(string_tensor): 872 """Convert a tensor back into a string 873 874 Parameters 875 ---------- 876 string_tensor : torch.Tensor 877 The tensor to convert. 878 879 Returns 880 ------- 881 str 882 The converted string. 883 884 """ 885 ords = string_tensor.tolist() 886 to_return = "" 887 # Doing block processing like this helps with memory errors 888 while len(ords) != 0: 889 remaining_ords = ords[100000:] 890 ords = ords[:100000] 891 to_append = "".join(map(chr, ords)) 892 to_return = to_return + to_append 893 ords = remaining_ords 894 return to_return
Convert a tensor back into a string
Parameters
- string_tensor (torch.Tensor): The tensor to convert.
Returns
- str: The converted string.
897def save_system(net, folder, name): 898 """Save the entire system 899 900 This saves the network itself as well as the tracker information 901 902 Parameters 903 ---------- 904 net : nn.Module 905 The network to save. 906 folder : str 907 The folder to save the network in. 908 name : str 909 The name to save the network under. 910 911 Returns 912 ------- 913 None 914 915 """ 916 if GPA.pc.get_verbose(): 917 print("saving system %s" % name) 918 temp = string_to_tensor(GPA.pai_tracker.to_string()) 919 if hasattr(net, "tracker_string"): 920 net.tracker_string = string_to_tensor(GPA.pai_tracker.to_string()).to( 921 next(net.parameters()).device 922 ) 923 else: 924 net.register_buffer( 925 "tracker_string", 926 string_to_tensor(GPA.pai_tracker.to_string()).to( 927 next(net.parameters()).device 928 ), 929 ) 930 # Before saving the tracker must be cleared to not contain pointers to the 931 # models modules 932 old_list = GPA.pai_tracker.neuron_module_vector 933 GPA.pai_tracker.neuron_module_vector = [] 934 save_net(net, folder, name) 935 GPA.pai_tracker.neuron_module_vector = old_list 936 pai_save_system(net, folder, name)
Save the entire system
This saves the network itself as well as the tracker information
Parameters
- net (nn.Module): The network to save.
- folder (str): The folder to save the network in.
- name (str): The name to save the network under.
Returns
- None
939def load_system( 940 net, 941 folder, 942 name, 943 load_from_restart=False, 944 switch_call=False, 945 load_from_manual_save=False, 946): 947 """Load the entire system 948 949 This is what should be used to load a saved system and restart training 950 951 Parameters 952 ---------- 953 net : nn.Module 954 The network to load into. 955 folder : str 956 The folder to load the network from. 957 name : str 958 The name to load the network from. 959 load_from_restart : bool, optional 960 Whether this is being loaded from an automatic restart, by default False 961 switch_call : bool, optional 962 Whether this is being called from a switch, by default False 963 load_from_manual_save : bool, optional 964 Whether this is being loaded from a manual save, by default False 965 966 Returns 967 ------- 968 nn.Module 969 The loaded network. 970 971 Notes 972 ----- 973 If you manually call save_system then load_from_manual_save should be True 974 975 """ 976 if GPA.pc.get_verbose(): 977 print("loading system %s" % name) 978 net = load_net(net, folder, name) 979 GPA.pai_tracker.reset_module_vector(net, load_from_restart) 980 981 GPA.pai_tracker.from_string(string_from_tensor(net.tracker_string)) 982 GPA.pai_tracker.saved_time = time.time() 983 GPA.pai_tracker.loaded = True 984 GPA.pai_tracker.member_vars["current_best_validation_score"] = 0 985 GPA.pai_tracker.member_vars["epoch_last_improved"] = GPA.pai_tracker.member_vars[ 986 "num_epochs_run" 987 ] 988 if GPA.pc.get_verbose(): 989 print( 990 "after loading epoch last improved is %d mode is %c" 991 % ( 992 GPA.pai_tracker.member_vars["epoch_last_improved"], 993 GPA.pai_tracker.member_vars["mode"], 994 ) 995 ) 996 997 # Saves always take place before the call to start_epoch so call it here 998 # when loading to correct off by 1 problems 999 if (not switch_call) and (not load_from_manual_save): 1000 GPA.pai_tracker.start_epoch(internal_call=True) 1001 return net
Load the entire system
This is what should be used to load a saved system and restart training
Parameters
- net (nn.Module): The network to load into.
- folder (str): The folder to load the network from.
- name (str): The name to load the network from.
- load_from_restart (bool, optional): Whether this is being loaded from an automatic restart, by default False
- switch_call (bool, optional): Whether this is being called from a switch, by default False
- load_from_manual_save (bool, optional): Whether this is being loaded from a manual save, by default False
Returns
- nn.Module: The loaded network.
Notes
If you manually call save_system then load_from_manual_save should be True
1004def load_pretrained_model( 1005 net, 1006 folder, 1007 name, 1008 remove_dendrite_scaffolding=False, 1009): 1010 """Load a pretrained perforated model and reset tracker for fresh training. 1011 1012 This function loads a pretrained model's weights and dendrite structure while 1013 resetting all tracker state (epochs, switch history, etc.) to start training 1014 from scratch on a new task. This is useful for transfer learning where you want 1015 pretrained weights but need fresh training dynamics. 1016 1017 Parameters 1018 ---------- 1019 net : nn.Module 1020 The network to load into. 1021 folder : str 1022 The folder containing the pretrained model. 1023 name : str 1024 The name of the checkpoint to load (e.g., 'best_model', 'beforeSwitch_0'). 1025 remove_dendrite_scaffolding : bool, optional 1026 If True, removes dendrite scaffolding for inference or finetuning without 1027 adding more dendrites using blockwise_network and refresh_net. Default False. 1028 1029 Returns 1030 ------- 1031 nn.Module 1032 The loaded network with reset tracker state. 1033 1034 Examples 1035 -------- 1036 Load pretrained weights for continued dendrite training: 1037 >>> model = load_pretrained_model(model, "pretrained-prefc", "beforeSwitch_0") 1038 1039 Load pretrained weights for finetuning without adding more dendrites: 1040 >>> model = load_pretrained_model(model, "pretrained-prefc", "best_model", 1041 ... remove_dendrite_scaffolding=True) 1042 1043 Notes 1044 ----- 1045 This function: 1046 - Loads model weights and dendrite structure from checkpoint 1047 - Resets all epoch counters to -1 (will become 0 after first start_epoch) 1048 - Resets switch history and validation score tracking 1049 - Clears accuracy/loss history arrays 1050 - Optionally removes dendrite scaffolding (no more dendrite additions) 1051 1052 The tracker is reset to behave as if starting fresh training, while keeping 1053 the learned weights and dendrite structure from the pretrained model. 1054 """ 1055 from perforatedai import globals_perforatedai as GPA 1056 1057 if GPA.pc.get_verbose(): 1058 print(f"Loading pretrained model from {folder}/{name}") 1059 1060 # Load the model weights and dendrite structure 1061 net = load_system(net, folder, name, load_from_manual_save=True) 1062 1063 if GPA.pc.get_verbose(): 1064 print("Resetting tracker state for fresh training...") 1065 1066 # Reset structural training state to true initial values. 1067 # Keeping pretrained architecture/weights while zeroing cycle counters avoids 1068 # stale dendrite bookkeeping referencing empty score buffers. 1069 GPA.pai_tracker.reset_module_vector(net, load_from_restart=True) 1070 GPA.pai_tracker.member_vars["mode"] = "n" 1071 GPA.pai_tracker.member_vars["num_dendrites_added"] = 0 1072 GPA.pai_tracker.member_vars["num_dendrites_integrated"] = 0 1073 GPA.pai_tracker.member_vars["num_cycles"] = 0 1074 GPA.pai_tracker.member_vars["num_dendrite_tries"] = 0 1075 GPA.pai_tracker.member_vars["current_n_set_global_best"] = True 1076 1077 # Reset epoch counters 1078 GPA.pai_tracker.member_vars["num_epochs_run"] = -1 1079 GPA.pai_tracker.member_vars["total_epochs_run"] = -1 1080 GPA.pai_tracker.member_vars["epoch_last_improved"] = 0 1081 GPA.pai_tracker.member_vars["last_switch"] = 0 1082 GPA.pai_tracker.member_vars["manual_train_switch"] = False 1083 1084 # Reset switch history 1085 GPA.pai_tracker.member_vars["switch_epochs"] = [] 1086 GPA.pai_tracker.member_vars["n_switch_epochs"] = [] 1087 GPA.pai_tracker.member_vars["p_switch_epochs"] = [] 1088 GPA.pai_tracker.member_vars["param_counts"] = [] 1089 1090 # Reset validation scores and tracking 1091 GPA.pai_tracker.member_vars["current_best_validation_score"] = 0 1092 GPA.pai_tracker.member_vars["global_best_validation_score"] = 0 1093 GPA.pai_tracker.member_vars["running_accuracy"] = 0 1094 1095 # Clear accuracy/loss history arrays 1096 GPA.pai_tracker.member_vars["accuracies"] = [] 1097 GPA.pai_tracker.member_vars["last_improved_accuracies"] = [] 1098 GPA.pai_tracker.member_vars["test_accuracies"] = [] 1099 GPA.pai_tracker.member_vars["n_accuracies"] = [] 1100 GPA.pai_tracker.member_vars["p_accuracies"] = [] 1101 GPA.pai_tracker.member_vars["running_accuracies"] = [] 1102 GPA.pai_tracker.member_vars["training_loss"] = [] 1103 GPA.pai_tracker.member_vars["training_learning_rates"] = [] 1104 GPA.pai_tracker.member_vars["test_scores"] = [] 1105 1106 # Clear extra scores 1107 GPA.pai_tracker.member_vars["extra_scores"] = {} 1108 GPA.pai_tracker.member_vars["extra_scores_without_graphing"] = {} 1109 GPA.pai_tracker.member_vars["n_extra_scores"] = {} 1110 1111 # Keep per-layer dendrite score buffers initialized by reset_module_vector. 1112 1113 # Clear timing arrays 1114 GPA.pai_tracker.member_vars["n_epoch_times"] = [] 1115 GPA.pai_tracker.member_vars["p_epoch_times"] = [] 1116 GPA.pai_tracker.member_vars["n_train_times"] = [] 1117 GPA.pai_tracker.member_vars["p_train_times"] = [] 1118 GPA.pai_tracker.member_vars["n_val_times"] = [] 1119 GPA.pai_tracker.member_vars["p_val_times"] = [] 1120 1121 # Clear overwritten tracking 1122 GPA.pai_tracker.member_vars["overwritten_extras"] = [] 1123 GPA.pai_tracker.member_vars["overwritten_vals"] = [] 1124 GPA.pai_tracker.member_vars["overwritten_epochs"] = 0 1125 1126 # Reset learning rate search state 1127 GPA.pai_tracker.member_vars["initial_lr_test_epoch_count"] = -1 1128 GPA.pai_tracker.member_vars["current_n_learning_rate_initial_skip_steps"] = 0 1129 GPA.pai_tracker.member_vars["last_max_learning_rate_steps"] = 0 1130 GPA.pai_tracker.member_vars["last_max_learning_rate_value"] = -1 1131 GPA.pai_tracker.member_vars["current_cycle_lr_max_scores"] = [] 1132 GPA.pai_tracker.member_vars["current_step_count"] = 0 1133 GPA.pai_tracker.member_vars["committed_to_initial_rate"] = True 1134 GPA.pai_tracker.member_vars["best_mean_score_improved_this_epoch"] = 0 1135 GPA.pai_tracker.member_vars["step_status"] = TPA.STEP_CLEARED 1136 1137 # Reset saved time 1138 GPA.pai_tracker.start_time = time.time() 1139 GPA.pai_tracker.saved_time = 0 1140 1141 # Match tracker initialization behavior so first validation uses epoch 0. 1142 GPA.pai_tracker.start_epoch(internal_call=True) 1143 1144 if GPA.pc.get_verbose(): 1145 print( 1146 f"Tracker reset complete. Dendrites: {GPA.pai_tracker.member_vars['num_dendrites_integrated']}, " 1147 f"Mode: {GPA.pai_tracker.member_vars['mode']}" 1148 ) 1149 1150 # Optionally remove dendrite scaffolding 1151 if remove_dendrite_scaffolding: 1152 if GPA.pc.get_verbose(): 1153 print("Removing dendrite scaffolding (no dendrite additions)...") 1154 1155 from perforatedai import blockwise_perforatedai as BPA 1156 from perforatedai import clean_perforatedai as CPA 1157 1158 net = BPA.blockwise_network(net) 1159 net = CPA.refresh_net(net) 1160 1161 if GPA.pc.get_verbose(): 1162 print("Dendrite scaffolding removed. Model ready for inference or finetuning.") 1163 1164 return net
Load a pretrained perforated model and reset tracker for fresh training.
This function loads a pretrained model's weights and dendrite structure while resetting all tracker state (epochs, switch history, etc.) to start training from scratch on a new task. This is useful for transfer learning where you want pretrained weights but need fresh training dynamics.
Parameters
- net (nn.Module): The network to load into.
- folder (str): The folder containing the pretrained model.
- name (str): The name of the checkpoint to load (e.g., 'best_model', 'beforeSwitch_0').
- remove_dendrite_scaffolding (bool, optional): If True, removes dendrite scaffolding for inference or finetuning without adding more dendrites using blockwise_network and refresh_net. Default False.
Returns
- nn.Module: The loaded network with reset tracker state.
Examples
Load pretrained weights for continued dendrite training:
>>> model = load_pretrained_model(model, "pretrained-prefc", "beforeSwitch_0")
Load pretrained weights for finetuning without adding more dendrites:
>>> model = load_pretrained_model(model, "pretrained-prefc", "best_model",
... remove_dendrite_scaffolding=True)
Notes
This function:
- Loads model weights and dendrite structure from checkpoint
- Resets all epoch counters to -1 (will become 0 after first start_epoch)
- Resets switch history and validation score tracking
- Clears accuracy/loss history arrays
- Optionally removes dendrite scaffolding (no more dendrite additions)
The tracker is reset to behave as if starting fresh training, while keeping the learned weights and dendrite structure from the pretrained model.
1173def save_model_with_weight_tying(model, filepath): 1174 """Save model with safetensors while handling weight tying automatically 1175 1176 Parameters 1177 ---------- 1178 model : Any PyTorch Module. 1179 filepath : Path to filename. 1180 1181 Returns 1182 ------- 1183 dict[str, str] 1184 Mapping from secondary parameter keys to their primary tied key. 1185 """ 1186 state_dict = model.state_dict() 1187 1188 # Find all weight tied parameters 1189 tensor_to_keys = defaultdict(list) 1190 for key, tensor in state_dict.items(): 1191 # Use tensor data pointer as unique identifier 1192 tensor_id = tensor.data_ptr() 1193 tensor_to_keys[tensor_id].append(key) 1194 1195 # Find tied weights (tensors referenced by multiple keys) 1196 tied_weights = {} 1197 keys_to_remove = set() 1198 for tensor_id, keys in tensor_to_keys.items(): 1199 if len(keys) > 1 and not tensor_id == 0: 1200 # Multiple keys reference the same tensor - this is weight tying 1201 # Sort keys for deterministic ordering 1202 keys = sorted(keys) 1203 primary_key = keys[0] # Keep the first key 1204 for secondary_key in keys[1:]: 1205 tied_weights[secondary_key] = primary_key 1206 keys_to_remove.add(secondary_key) 1207 1208 # Remove tied weights from state_dict (keep only primary references) 1209 filtered_state_dict = { 1210 k: v for k, v in state_dict.items() if k not in keys_to_remove 1211 } 1212 1213 # Create metadata for weight tying information 1214 metadata = {} 1215 if tied_weights: 1216 # Store weight tying info as JSON string in metadata 1217 metadata["weight_tying"] = json.dumps(tied_weights) 1218 save_file(filtered_state_dict, filepath, metadata=metadata) 1219 print(f"Saved model with {len(tied_weights)} weight tying relationships") 1220 return tied_weights
Save model with safetensors while handling weight tying automatically
Parameters
model (Any PyTorch Module.):
filepath (Path to filename.):
Returns
- dict[str, str]: Mapping from secondary parameter keys to their primary tied key.
1223def load_model_with_weight_tying(model, filepath): 1224 """Load model from safetensors while restoring weight tying 1225 1226 Parameters 1227 ---------- 1228 model : Any PyTorch module. 1229 filepath : Path to model file. 1230 1231 Returns 1232 ------- 1233 Loaded model. 1234 """ 1235 with safe_open(filepath, framework="pt") as f: 1236 metadata = f.metadata() 1237 state_dict = {key: f.get_tensor(key) for key in f.keys()} 1238 1239 # Restore weight tying if metadata exists 1240 tied_weights = {} 1241 if metadata and "weight_tying" in metadata: 1242 tied_weights = json.loads(metadata["weight_tying"]) 1243 for secondary_key, primary_key in tied_weights.items(): 1244 if primary_key in state_dict: 1245 # Restore the tied reference 1246 state_dict[secondary_key] = state_dict[primary_key] 1247 print(f"Restored weight tying: {secondary_key} -> {primary_key}") 1248 1249 # Handle tracker_string loading with flexible key matching 1250 tracker_key = None 1251 if "tracker_string" in state_dict: 1252 tracker_key = "tracker_string" 1253 else: 1254 # Search for keys containing "tracker_string" 1255 tracker_keys = [key for key in state_dict.keys() if "tracker_string" in key] 1256 if len(tracker_keys) == 1: 1257 tracker_key = tracker_keys[0] 1258 elif len(tracker_keys) > 1: 1259 print(f"Error: Multiple tracker_string keys found: {tracker_keys}") 1260 pdb.set_trace() 1261 else: 1262 print("Error: No tracker_string found in state_dict") 1263 1264 if tracker_key is not None and hasattr(model, "tracker_string"): 1265 model.tracker_string = state_dict[tracker_key] 1266 1267 model.load_state_dict(state_dict) 1268 return model
Load model from safetensors while restoring weight tying
Parameters
model (Any PyTorch module.):
filepath (Path to model file.):
Returns
- Loaded model.
1271def save_net(net, folder, name): 1272 """Save the network 1273 1274 This is called within save_system after the tracker has been 1275 turned into a single tensor to be saved as a part of the network 1276 1277 Parameters 1278 ---------- 1279 net : nn.Module 1280 The network to save. 1281 folder : str 1282 The folder to save the network in. 1283 name : str 1284 The name to save the network under. 1285 1286 Returns 1287 ------- 1288 None 1289 1290 """ 1291 # If running a DDP only save with first thread 1292 if "RANK" in os.environ: 1293 if int(os.environ["RANK"]) != 0: 1294 return 1295 if not os.path.isdir(folder): 1296 os.makedirs(folder) 1297 save_point = folder + "/" 1298 if not os.path.isdir(save_point): 1299 os.mkdir(save_point) 1300 for param in net.parameters(): 1301 param.data = param.data.contiguous() 1302 if GPA.pc.get_using_safe_tensors(): 1303 if GPA.pc.get_weight_tying_experimental(): 1304 save_model_with_weight_tying(net, save_point + name + ".pt") 1305 else: 1306 # Strip the . so that the naming is the same for everywhere but it works with state_dict naming 1307 not_save = [ns.lstrip('.') for ns in GPA.pc.get_module_names_to_not_save()] 1308 state_dict = {k: v for k, v in net.state_dict().items() 1309 if not any(k.startswith(ns) for ns in not_save)} 1310 save_file(state_dict, save_point + name + ".pt") 1311 else: 1312 torch.save(net, save_point + name + ".pt")
Save the network
This is called within save_system after the tracker has been turned into a single tensor to be saved as a part of the network
Parameters
- net (nn.Module): The network to save.
- folder (str): The folder to save the network in.
- name (str): The name to save the network under.
Returns
- None
1359def save_pai_net(net, folder, name): 1360 """Save the final pai network 1361 1362 This can be called after training to save the final network 1363 with all scaffolding removed so only the refined weights remain 1364 1365 Parameters 1366 ---------- 1367 net : nn.Module 1368 The network to save. 1369 folder : str 1370 The folder to save the network in. 1371 name : str 1372 The name to save the network under. 1373 1374 Returns 1375 ------- 1376 None 1377 1378 """ 1379 # if running a DDP only save with first thread 1380 if "RANK" in os.environ: 1381 if int(os.environ["RANK"]) != 0: 1382 return 1383 1384 # print('calling save: %s' % name) 1385 # GPA.pai_tracker.archive_layer() 1386 # These deep copys are required or the real model will also have its layers replaced 1387 net = prepare_final_model(net) 1388 if not os.path.isdir(folder): 1389 os.makedirs(folder) 1390 save_point = folder + "/" 1391 if not os.path.isdir(save_point): 1392 os.mkdir(save_point) 1393 1394 if GPA.pc.get_using_safe_tensors(): 1395 if GPA.pc.get_weight_tying_experimental(): 1396 save_model_with_weight_tying(net, save_point + name + "_pai.pt") 1397 else: 1398 save_file(net.state_dict(), save_point + name + "_pai.pt") 1399 else: 1400 torch.save(net, save_point + name + "_pai.pt")
Save the final pai network
This can be called after training to save the final network with all scaffolding removed so only the refined weights remain
Parameters
- net (nn.Module): The network to save.
- folder (str): The folder to save the network in.
- name (str): The name to save the network under.
Returns
- None
1403def manual_load_state_dict(model, state_dict): 1404 """Load a state dict into a model key-by-key with relaxed checks. 1405 1406 Parameters 1407 ---------- 1408 model : nn.Module 1409 Model receiving parameters. 1410 state_dict : dict 1411 Source parameter dictionary. 1412 1413 Notes 1414 ----- 1415 Keys configured in ``module_names_to_not_save`` are skipped. 1416 1417 Returns 1418 ------- 1419 None 1420 This function does not return a value. 1421 """ 1422 own_state = model.state_dict() 1423 not_save = [ns.lstrip('.') for ns in GPA.pc.get_module_names_to_not_save()] 1424 for name, param in state_dict.items(): 1425 if any(name.startswith(ns) for ns in not_save): 1426 print("skipping loading %s based on module_names_to_not_save" % name) 1427 continue 1428 if name not in own_state: 1429 print(f"Warning: {name} not found in model state_dict") 1430 continue 1431 if isinstance(param, torch.nn.Parameter): 1432 # Backwards compatibility for serialized parameters 1433 param = param.data 1434 try: 1435 own_state[name].copy_(param) 1436 except Exception as e: 1437 print(f"Error loading {name}: {e}") 1438 print("Manual load complete")
Load a state dict into a model key-by-key with relaxed checks.
Parameters
- model (nn.Module): Model receiving parameters.
- state_dict (dict): Source parameter dictionary.
Notes
Keys configured in module_names_to_not_save are skipped.
Returns
- None: This function does not return a value.
1441def load_net(net, folder, name): 1442 """load the network 1443 1444 This is called within load_system after the tracker has been 1445 loaded 1446 1447 Parameters 1448 ---------- 1449 net : nn.Module 1450 The network to save. 1451 folder : str 1452 The folder to save the network in. 1453 name : str 1454 The name to save the network under. 1455 1456 Returns 1457 ------- 1458 nn.Module 1459 The loaded network. 1460 1461 """ 1462 save_point = folder + "/" 1463 if GPA.pc.get_using_safe_tensors(): 1464 model_path = save_point + name + ".pt" 1465 if GPA.pc.get_weight_tying_experimental(): 1466 return load_model_with_weight_tying(net, model_path) 1467 else: 1468 try: 1469 with safe_open(model_path, framework="pt") as f: 1470 metadata = f.metadata() 1471 if metadata and "weight_tying" in metadata: 1472 return load_model_with_weight_tying(net, model_path) 1473 except Exception: 1474 pass 1475 state_dict = load_file(model_path) 1476 else: 1477 # Different versions of torch require this change 1478 try: 1479 state_dict = torch.load( 1480 save_point + name + ".pt", 1481 map_location=torch.device("cpu"), 1482 weights_only=False, 1483 ).state_dict() 1484 except: 1485 try: 1486 state_dict = torch.load( 1487 save_point + name + ".pt", map_location=torch.device("cpu") 1488 ).state_dict() 1489 except: 1490 state_dict = torch.load( 1491 save_point + name + ".pt", map_location=torch.device("cpu") 1492 ) 1493 return load_net_from_dict(net, state_dict)
load the network
This is called within load_system after the tracker has been loaded
Parameters
- net (nn.Module): The network to save.
- folder (str): The folder to save the network in.
- name (str): The name to save the network under.
Returns
- nn.Module: The loaded network.
1496def get_module_base_name(module): 1497 """Normalize a wrapped module name for state-dict key lookup. 1498 1499 Parameters 1500 ---------- 1501 module : nn.Module 1502 Module containing a ``name`` attribute. 1503 1504 Returns 1505 ------- 1506 str 1507 Base name with leading dot and optional ``module.`` prefix removed. 1508 """ 1509 module_name = module.name 1510 # This should always be true 1511 if module_name[0] == ".": 1512 # strip "." 1513 module_name = module_name[1:] 1514 # If it was a dataparallel it will also have a module at the start 1515 # so strip that for loading 1516 if module_name[:6] == "module": 1517 module_name = module_name[7:] 1518 return module_name
Normalize a wrapped module name for state-dict key lookup.
Parameters
- module (nn.Module):
Module containing a
nameattribute.
Returns
- str: Base name with leading dot and optional
module.prefix removed.
1521def load_net_from_dict(net, state_dict): 1522 """load the network 1523 1524 This is called within load_net 1525 1526 Parameters 1527 ---------- 1528 net : nn.Module 1529 The network to save. 1530 state_dict : dict 1531 The state dictionary to load. 1532 1533 Returns 1534 ------- 1535 nn.Module 1536 The loaded network. 1537 1538 """ 1539 if GPA.pc.get_verbose(): 1540 print("loading net from dict") 1541 pai_modules = get_pai_modules(net, 0) 1542 if pai_modules == []: 1543 print( 1544 "PAI load_net and load_system uses a state_dict so it must be\n" 1545 "called with a net after perforate_model has been called" 1546 ) 1547 print( 1548 "This is being flagged because you are attempting to load a model\n" 1549 "that does not have any pai_modules in it. Confirm that you are calling\n" 1550 "perforate_model on the correct model, and the same model is the one\n" 1551 "being passed into add_validation_score" 1552 ) 1553 import pdb # This needs to be here for cython for some reason. 1554 pdb.set_trace() 1555 sys.exit(-1) 1556 if GPA.pc.get_verbose(): 1557 print( 1558 "setting up arrays and simulating cycles for %d pai modules" 1559 % len(pai_modules) 1560 ) 1561 not_save = GPA.pc.get_module_names_to_not_save() 1562 for module in pai_modules: 1563 if any(module.name.startswith(ns) for ns in not_save): 1564 print("skipping loading %s based on module_names_to_not_save" % module.name) 1565 continue 1566 # Set up name to be what will be saved in the state dict 1567 module_name = get_module_base_name(module) 1568 module.clear_dendrites() 1569 for tracker in module.dendrite_module.dendrite_values: 1570 try: 1571 tracker.setup_arrays( 1572 len( 1573 state_dict[ 1574 module_name + ".dendrite_module.dendrite_values.0.shape" 1575 ] 1576 ) 1577 ) 1578 except Exception as e: 1579 print(e) 1580 print( 1581 "This value is missing from the state dict\n" 1582 "When missing this value it typically means you\n" 1583 "converted a module but didn't actually use it in\n" 1584 "your forward and backward pass." 1585 ) 1586 print("module was: %s" % module.name) 1587 print("There are many reasons this can happen:") 1588 print( 1589 "\n1 - check your model definition and forward function and " 1590 "ensure this module is being used properly" 1591 ) 1592 print( 1593 "with GPA.pc.set_verbose(True) you can confirm this is the case if\n" 1594 'you do not see a "setting d shape for" this module at the first training batch.' 1595 ) 1596 print( 1597 "If this is the case, and it is correct to not be passing data through it\n" 1598 "Set it to be a tracked module with:\n" 1599 'GPA.pc.append_module_ids_to_track(["%s"]) to leave it out ' 1600 % module.name 1601 ) 1602 print( 1603 "\n2 - This can happen if you adjusted your model " 1604 "definition after calling perforate_model" 1605 ) 1606 print( 1607 "for example with torch.compile. If the module name " 1608 "printed above does not contain all modules leading " 1609 "to the main definition" 1610 ) 1611 print( 1612 "this is likely the case for your problem. Fix by " 1613 "calling perforate_model after all other model " 1614 "initialization steps" 1615 ) 1616 first_key = next(iter(state_dict.keys())) 1617 print( 1618 "\n3 - This can happen is if the model where you called perforate_model\n" 1619 "and the model within add_validation_score are not the same. \n" 1620 "Check if the module above and .%s have the same prefix\n" 1621 % first_key 1622 ) 1623 print( 1624 "if one starts with .model or .base etc and the other does not, this is the problem." 1625 ) 1626 1627 print( 1628 "\n4 - If you are using this module but then not actually including\n" 1629 "the correct output tensor in the forward. For example\n" 1630 "if you are using an LSTM and forwarding hidden instead of otput\n" 1631 "but your processors are set up to work with output" 1632 ) 1633 print( 1634 "\n5 - if you are not properly calling backward at all." 1635 " If this is the first module in your network it is more" 1636 "likely this is the problem." 1637 "One check in these cases is to make sure you do not call an initial validation score" 1638 "before the first backward call.\nIf you do this, while testing_dendrite_capacity is True" 1639 "this error will be triggered." 1640 ) 1641 print( 1642 "\n6 - You have converted a module that is in a frozen" 1643 " part of the network and thus no gradients are flowing" 1644 ) 1645 print( 1646 "\n7 - You are running multiple experiments at once with the same save_name." 1647 " When running concurrent trials be sure to add save_name=<unique_name> to perforate_model." 1648 ) 1649 import pdb # This needs to be here for cython for some reason. 1650 pdb.set_trace() 1651 1652 # Perform as many cycles as the state dict has 1653 num_cycles = int(state_dict[module_name + ".dendrite_module.num_cycles"].item()) 1654 if num_cycles > 0: 1655 simulate_cycles(module, num_cycles, doing_pai=True) 1656 # Handle tracker_string loading with flexible key matching 1657 tracker_key = None 1658 if "tracker_string" in state_dict: 1659 tracker_key = "tracker_string" 1660 else: 1661 # Search for keys containing "tracker_string" 1662 tracker_keys = [key for key in state_dict.keys() if "tracker_string" in key] 1663 if len(tracker_keys) == 1: 1664 tracker_key = tracker_keys[0] 1665 elif len(tracker_keys) > 1: 1666 print(f"Error: Multiple tracker_string keys found: {tracker_keys}") 1667 import pdb # This needs to be here for cython for some reason. 1668 pdb.set_trace() 1669 else: 1670 print("Error: No tracker_string found in state_dict") 1671 import pdb # This needs to be here for cython for some reason. 1672 pdb.set_trace() 1673 1674 if hasattr(net, "tracker_string"): 1675 net.tracker_string = state_dict[tracker_key] 1676 else: 1677 net.register_buffer("tracker_string", state_dict[tracker_key]) 1678 try: 1679 load_result = net.load_state_dict(state_dict, strict=False) 1680 not_save_state_names = [ns.lstrip('.') for ns in not_save] 1681 1682 def is_ignored_key(key): 1683 """Check whether a state-dict key should be ignored. 1684 1685 Parameters 1686 ---------- 1687 key : str 1688 State-dict key to test. 1689 1690 Returns 1691 ------- 1692 bool 1693 ``True`` when key belongs to a not-saved namespace. 1694 """ 1695 return any(key.startswith(ns) for ns in not_save_state_names) 1696 1697 missing_keys = [key for key in load_result.missing_keys if not is_ignored_key(key)] 1698 unexpected_keys = [key for key in load_result.unexpected_keys if not is_ignored_key(key)] 1699 1700 if GPA.pc.get_strict_loading() and (missing_keys or unexpected_keys): 1701 raise RuntimeError( 1702 "Error(s) in loading state_dict for %s:\n\tMissing key(s) in state_dict: %s. \n\tUnexpected key(s) in state_dict: %s." 1703 % (type(net).__name__, missing_keys, unexpected_keys) 1704 ) 1705 except Exception as e: 1706 """ 1707 When modules have high depth to them (i.e. modules within modules not number of layers) 1708 PyTorch can have trouble loading state dicts even when they are correct. 1709 This is a workaround to manually load the state dict if this happens. 1710 """ 1711 filtered_net_keys = { 1712 key 1713 for key in net.state_dict().keys() 1714 if not any(key.startswith(ns.lstrip('.')) for ns in not_save) 1715 } 1716 if filtered_net_keys == set(state_dict.keys()): 1717 print("Attempting manual loading of state_dict") 1718 manual_load_state_dict(net, state_dict) 1719 else: 1720 print(f"Error loading state_dict: {e}") 1721 print("If the error is due to missing keys (e.g., from code changes), you can try:") 1722 print(" GPA.pc.set_strict_loading(False)") 1723 print(" Do not change this unless you are certain the missing keys are not important to load and are expected due to code changes or arch changes.") 1724 print("\ntype 'c' to print full state dicts\n") 1725 import pdb # This needs to be here for cython for some reason. 1726 pdb.set_trace() 1727 print("net state dict is:") 1728 print(net.state_dict()) 1729 print("loaded state dict is:") 1730 print(state_dict) 1731 print( 1732 "Try to check differences. Likely is caused by a module not " 1733 "being converted that should be or vice versa" 1734 ) 1735 pdb.set_trace() 1736 net.to(GPA.pc.get_device()) 1737 return net
load the network
This is called within load_net
Parameters
- net (nn.Module): The network to save.
- state_dict (dict): The state dictionary to load.
Returns
- nn.Module: The loaded network.
1740def pai_save_system(net, folder, name): 1741 """Save the entire system with scaffolding removed 1742 1743 This is used for the final network for inference after training 1744 1745 Parameters 1746 ---------- 1747 net : nn.Module 1748 The network to save. 1749 folder : str 1750 The folder to save the network in. 1751 name : str 1752 The name to save the network under. 1753 1754 Returns 1755 ------- 1756 None 1757 1758 """ 1759 net.member_vars = {} 1760 for member_var in GPA.pai_tracker.member_vars: 1761 if member_var == "scheduler_instance" or member_var == "optimizer_instance": 1762 continue 1763 net.member_vars[member_var] = GPA.pai_tracker.member_vars[member_var] 1764 pai_save_net(net, folder, name)
Save the entire system with scaffolding removed
This is used for the final network for inference after training
Parameters
- net (nn.Module): The network to save.
- folder (str): The folder to save the network in.
- name (str): The name to save the network under.
Returns
- None
1767def deep_copy_pai(net): 1768 """Deep copy a PAI network 1769 1770 1771 Parameters 1772 ---------- 1773 net : nn.Module 1774 The network to copy. 1775 1776 Returns 1777 ------- 1778 nn.Module 1779 The copied network. 1780 1781 Notes 1782 ---- 1783 This is required because processors must be cleared before calling copy 1784 1785 """ 1786 # Dont check this stuff if its before the perforate_model has been called and you're just copying a regular model 1787 if(GPA.pai_tracker != []): 1788 # Clear gradients before saving the model 1789 if ((GPA.pai_tracker.member_vars["optimizer_instance"]) is not None) and ( 1790 GPA.pai_tracker.member_vars["optimizer_instance"] != [] 1791 ): 1792 GPA.pai_tracker.member_vars["optimizer_instance"].zero_grad() 1793 GPA.pai_tracker.clear_all_processors() 1794 return copy.deepcopy(net)
Deep copy a PAI network
Parameters
- net (nn.Module): The network to copy.
Returns
- nn.Module: The copied network.
Notes
This is required because processors must be cleared before calling copy
1797def prepare_final_model(net): 1798 """Prepare model for final save by removing scaffolding. 1799 1800 This performs all cleanup steps to convert a PAI model with scaffolding 1801 into a clean final model ready for inference or distribution. 1802 1803 Parameters 1804 ---------- 1805 net : nn.Module 1806 The network to prepare. 1807 1808 Returns 1809 ------- 1810 nn.Module 1811 The cleaned model with scaffolding removed. 1812 """ 1813 # Deep copy and clean the model (removes scaffolding) 1814 net = deep_copy_pai(net) 1815 net = BPA.blockwise_network(net) 1816 net = deep_copy_pai(net) 1817 net = CL.refresh_net(net) 1818 1819 # Remove tracker_string (not needed for final model) 1820 if hasattr(net, "tracker_string"): 1821 del net.tracker_string 1822 1823 # Make parameters contiguous 1824 for param in net.parameters(): 1825 param.data = param.data.contiguous() 1826 1827 return net
Prepare model for final save by removing scaffolding.
This performs all cleanup steps to convert a PAI model with scaffolding into a clean final model ready for inference or distribution.
Parameters
- net (nn.Module): The network to prepare.
Returns
- nn.Module: The cleaned model with scaffolding removed.
1830def pai_save_net(net, folder, name): 1831 """Save the entire system with scaffolding removed 1832 1833 This is called within pai_save_system after the tracker has been 1834 turned into a single tensor to be saved as a part of the network 1835 1836 1837 Parameters 1838 ---------- 1839 net : nn.Module 1840 The network to save. 1841 folder : str 1842 The folder to save the network in. 1843 name : str 1844 The name to save the network under. 1845 1846 Returns 1847 ------- 1848 None 1849 1850 Notes 1851 ---- 1852 For open source implementation this is not as important since 1853 minimal values are already being used. 1854 1855 """ 1856 1857 if GPA.pc.get_perforated_backpropagation(): 1858 UPB.pb_save_net(net, folder, name) 1859 else: 1860 return
Save the entire system with scaffolding removed
This is called within pai_save_system after the tracker has been turned into a single tensor to be saved as a part of the network
Parameters
- net (nn.Module): The network to save.
- folder (str): The folder to save the network in.
- name (str): The name to save the network under.
Returns
- None
Notes
For open source implementation this is not as important since minimal values are already being used.
1863def simulate_cycles(module, num_cycles, doing_pai): 1864 """Simulate dendrite addition cycles 1865 1866 Simulate the back and forth processes of adding dendrites to build a 1867 pretrained dendrite model before loading weights. Required for loading 1868 dendrite save files from non dendrite initial models. 1869 1870 Parameters 1871 ---------- 1872 module : PA.PAINeuronModule 1873 The module to simulate cycles on. 1874 num_cycles : int 1875 The number of cycles to simulate. 1876 doing_pai : bool 1877 Whether to actually do the simulation. 1878 1879 Returns 1880 ------- 1881 None 1882 1883 """ 1884 1885 check_skipped = GPA.pc.get_checked_skipped_modules() 1886 if doing_pai is False: 1887 return 1888 GPA.pc.set_checked_skipped_modules(True) 1889 mode = "n" 1890 for i in range(num_cycles): 1891 if mode == "n": 1892 module.set_mode("p") 1893 module.create_new_dendrite_module() 1894 mode = "p" 1895 else: 1896 module.set_mode("n") 1897 mode = "n" 1898 GPA.pc.set_checked_skipped_modules(check_skipped)
Simulate dendrite addition cycles
Simulate the back and forth processes of adding dendrites to build a pretrained dendrite model before loading weights. Required for loading dendrite save files from non dendrite initial models.
Parameters
- module (PA.PAINeuronModule): The module to simulate cycles on.
- num_cycles (int): The number of cycles to simulate.
- doing_pai (bool): Whether to actually do the simulation.
Returns
- None
1901def count_params(net): 1902 """Count the number of parameters in the network 1903 1904 If doing perforated backpropagation this calls the PB function 1905 which does not count scaffolding parameters since the final model 1906 will not have them. 1907 1908 Parameters 1909 ---------- 1910 net : nn.Module 1911 The network to count parameters in. 1912 1913 Returns 1914 ------- 1915 int 1916 The number of parameters in the network. 1917 1918 """ 1919 if GPA.pc.get_perforated_backpropagation(): 1920 return UPB.pb_count_params(net) 1921 parameters = net.named_parameters() 1922 unique_params = { 1923 p.data_ptr(): p for name, p in parameters if "parent_module" not in name 1924 }.values() 1925 return sum(p.numel() for p in unique_params)
Count the number of parameters in the network
If doing perforated backpropagation this calls the PB function which does not count scaffolding parameters since the final model will not have them.
Parameters
- net (nn.Module): The network to count parameters in.
Returns
- int: The number of parameters in the network.
1928def change_learning_modes(net, folder, name, doing_pai): 1929 """Change between neuron and dendrite learning modes 1930 1931 High level steps for entire system to switch back and forth between 1932 neuron learning and dendrite learning 1933 1934 Parameters 1935 ---------- 1936 net : nn.Module 1937 The network to change modes on. 1938 folder : str 1939 The folder to save/load the network in/from. 1940 name : str 1941 The name to save/load the network under. 1942 doing_pai : bool 1943 Whether to add dendrites when changing modes. 1944 1945 Returns 1946 ------- 1947 int 1948 The number of parameters in the network. 1949 1950 Notes 1951 ----- 1952 If doing_pai is False this just allows training to continue longer rather than early stopping 1953 1954 """ 1955 # If not adding dendrites this just allows training to continue longer with flags 1956 # every time early stopping should be occurring 1957 if doing_pai is False: 1958 GPA.pai_tracker.member_vars["switch_epochs"].append( 1959 GPA.pai_tracker.member_vars["num_epochs_run"] 1960 ) 1961 GPA.pai_tracker.member_vars["last_switch"] = GPA.pai_tracker.member_vars[ 1962 "switch_epochs" 1963 ][-1] 1964 GPA.pai_tracker.reset_vals_for_score_reset() 1965 return net 1966 if GPA.pai_tracker.member_vars["mode"] == "n": 1967 current_epoch = GPA.pai_tracker.member_vars["num_epochs_run"] 1968 overwritten_epochs = GPA.pai_tracker.member_vars["overwritten_epochs"] 1969 overwritten_extra = GPA.pai_tracker.member_vars["extra_scores"] 1970 if GPA.pc.get_drawing_pai(): 1971 overwritten_val = GPA.pai_tracker.member_vars["accuracies"] 1972 else: 1973 overwritten_val = GPA.pai_tracker.member_vars["neuron_accuracies"] 1974 """ 1975 If true don't load the best system 1976 because it will delete dendrites if the previous best was better than 1977 the current best 1978 """ 1979 if not GPA.pc.get_silent(): 1980 print("Importing best Model for switch to PA...") 1981 net = load_system(net, folder, name, switch_call=True) 1982 GPA.pai_tracker.set_dendrite_training() 1983 GPA.pai_tracker.member_vars["overwritten_epochs"] = overwritten_epochs 1984 GPA.pai_tracker.member_vars["overwritten_epochs"] += ( 1985 current_epoch - GPA.pai_tracker.member_vars["num_epochs_run"] 1986 ) 1987 GPA.pai_tracker.member_vars["total_epochs_run"] = ( 1988 GPA.pai_tracker.member_vars["num_epochs_run"] 1989 + GPA.pai_tracker.member_vars["overwritten_epochs"] 1990 ) 1991 1992 if GPA.pc.get_save_old_graph_scores(): 1993 GPA.pai_tracker.member_vars["overwritten_extras"].append(overwritten_extra) 1994 GPA.pai_tracker.member_vars["overwritten_vals"].append(overwritten_val) 1995 else: 1996 GPA.pai_tracker.member_vars["overwritten_extras"] = [overwritten_extra] 1997 GPA.pai_tracker.member_vars["overwritten_vals"] = [overwritten_val] 1998 if GPA.pc.get_drawing_pai(): 1999 GPA.pai_tracker.member_vars["n_switch_epochs"].append( 2000 GPA.pai_tracker.member_vars["num_epochs_run"] 2001 ) 2002 else: 2003 if len(GPA.pai_tracker.member_vars["switch_epochs"]) == 0: 2004 GPA.pai_tracker.member_vars["n_switch_epochs"].append( 2005 GPA.pai_tracker.member_vars["num_epochs_run"] 2006 ) 2007 else: 2008 GPA.pai_tracker.member_vars["n_switch_epochs"].append( 2009 GPA.pai_tracker.member_vars["n_switch_epochs"][-1] 2010 + ( 2011 (GPA.pai_tracker.member_vars["num_epochs_run"]) 2012 - (GPA.pai_tracker.member_vars["switch_epochs"][-1]) 2013 ) 2014 ) 2015 2016 GPA.pai_tracker.member_vars["switch_epochs"].append( 2017 GPA.pai_tracker.member_vars["num_epochs_run"] 2018 ) 2019 GPA.pai_tracker.member_vars["last_switch"] = GPA.pai_tracker.member_vars[ 2020 "switch_epochs" 2021 ][-1] 2022 2023 # Because open source version is only doing neuron training for 2024 # gradient descent dendrites, switch back to n mode right away 2025 if ( 2026 not GPA.pc.get_perforated_backpropagation() 2027 ) or GPA.pc.get_no_extra_n_modes(): 2028 net = change_learning_modes(net, folder, name, doing_pai) 2029 else: 2030 if not GPA.pc.get_silent(): 2031 print("Switching back to N...") 2032 set_best = GPA.pai_tracker.member_vars["current_n_set_global_best"] 2033 GPA.pai_tracker.set_neuron_training() 2034 if len(GPA.pai_tracker.member_vars["p_switch_epochs"]) == 0: 2035 GPA.pai_tracker.member_vars["p_switch_epochs"].append( 2036 ( 2037 (GPA.pai_tracker.member_vars["num_epochs_run"] - 1) 2038 - (GPA.pai_tracker.member_vars["switch_epochs"][-1]) 2039 ) 2040 ) 2041 else: 2042 GPA.pai_tracker.member_vars["p_switch_epochs"].append( 2043 GPA.pai_tracker.member_vars["p_switch_epochs"][-1] 2044 + ( 2045 (GPA.pai_tracker.member_vars["num_epochs_run"]) 2046 - (GPA.pai_tracker.member_vars["switch_epochs"][-1]) 2047 ) 2048 ) 2049 GPA.pai_tracker.member_vars["switch_epochs"].append( 2050 GPA.pai_tracker.member_vars["num_epochs_run"] 2051 ) 2052 GPA.pai_tracker.member_vars["last_switch"] = GPA.pai_tracker.member_vars[ 2053 "switch_epochs" 2054 ][-1] 2055 # Will be false for open source implementation 2056 if GPA.pc.get_retain_all_dendrites() or ( 2057 GPA.pc.get_learn_dendrites_live() and set_best 2058 ): 2059 if not GPA.pc.get_silent(): 2060 print( 2061 "Saving model before starting normal training to " 2062 "retain PBNodes regardless of next N Phase results" 2063 ) 2064 save_system(net, folder, name) 2065 # if its just doing P for learn PAI live then switch back immediately 2066 if GPA.pc.get_perforated_backpropagation() and GPA.pc.get_no_extra_n_modes(): 2067 net = change_learning_modes(net, folder, name, doing_pai) 2068 2069 GPA.pai_tracker.member_vars["param_counts"].append(count_params(net)) 2070 2071 return net
Change between neuron and dendrite learning modes
High level steps for entire system to switch back and forth between neuron learning and dendrite learning
Parameters
- net (nn.Module): The network to change modes on.
- folder (str): The folder to save/load the network in/from.
- name (str): The name to save/load the network under.
- doing_pai (bool): Whether to add dendrites when changing modes.
Returns
- int: The number of parameters in the network.
Notes
If doing_pai is False this just allows training to continue longer rather than early stopping
2074def find_param_name_by_id(model, param_id): 2075 """ 2076 This is only used for debugging. 2077 Return the fully-qualified parameter name (e.g. "layer1.conv.weight") 2078 for the parameter whose id matches param_id. Returns None if not found. 2079 2080 This uses model.named_parameters(), which already recurses through submodules. 2081 2082 Parameters 2083 ---------- 2084 model : Model to look for param id. 2085 param_id : pointer to a parameter. 2086 2087 Returns 2088 ------- 2089 String representing the module within the model or None if not found. 2090 """ 2091 for name, p in model.named_parameters(recurse=True): 2092 if id(p) == param_id: 2093 return "." + name 2094 return None
This is only used for debugging. Return the fully-qualified parameter name (e.g. "layer1.conv.weight") for the parameter whose id matches param_id. Returns None if not found.
This uses model.named_parameters(), which already recurses through submodules.
Parameters
model (Model to look for param id.):
param_id (pointer to a parameter.):
Returns
- String representing the module within the model or None if not found.
2097def add_method_delegation_to_module(wrapper_module, method_name): 2098 """Add delegating methods to a wrapper module that has a main_module attribute. 2099 2100 This adds the specified methods to the wrapper module instance so they 2101 properly delegate to the wrapped main_module. Works for any wrapper module 2102 (TrackedNeuronModule, PAINeuronModule, etc.) that has a main_module attribute. 2103 2104 Args: 2105 wrapper_module: A wrapper module instance with a main_module attribute 2106 method_name: The method name to delegate (e.g., '_gradient_checkpointing_func') 2107 2108 Parameters 2109 ---------- 2110 wrapper_module : PyTorch Module that contains a sub module. 2111 method_name : Name of method to wrap. 2112 2113 Returns 2114 ------- 2115 None 2116 """ 2117 import types 2118 2119 if hasattr(wrapper_module.main_module, method_name): 2120 # Create a delegating method that forwards to main_module 2121 def make_delegated_method(name): 2122 """Create a bound delegation function for a given attribute name. 2123 2124 Parameters 2125 ---------- 2126 name : str 2127 Attribute name to forward to ``main_module``. 2128 2129 Returns 2130 ------- 2131 callable 2132 Function that delegates access or invocation. 2133 """ 2134 def delegated_method(self, *args, **kwargs): 2135 """Delegate attribute access or method call to ``main_module``. 2136 2137 Parameters 2138 ---------- 2139 *args : tuple 2140 Positional arguments forwarded to delegated callables. 2141 **kwargs : dict 2142 Keyword arguments forwarded to delegated callables. 2143 2144 Returns 2145 ------- 2146 Any 2147 Delegated attribute value or method result. 2148 """ 2149 main_module_attr = getattr(self.main_module, name, None) 2150 if main_module_attr is None: 2151 raise AttributeError( 2152 f"'{type(self.main_module).__name__}' object has no attribute '{name}'" 2153 ) 2154 if callable(main_module_attr): 2155 return main_module_attr(*args, **kwargs) 2156 return main_module_attr 2157 2158 return delegated_method 2159 2160 # Bind it to this specific instance 2161 setattr( 2162 wrapper_module, 2163 method_name, 2164 types.MethodType(make_delegated_method(method_name), wrapper_module), 2165 )
Add delegating methods to a wrapper module that has a main_module attribute.
This adds the specified methods to the wrapper module instance so they properly delegate to the wrapped main_module. Works for any wrapper module (TrackedNeuronModule, PAINeuronModule, etc.) that has a main_module attribute.
Args: wrapper_module: A wrapper module instance with a main_module attribute method_name: The method name to delegate (e.g., '_gradient_checkpointing_func')
Parameters
wrapper_module (PyTorch Module that contains a sub module.):
method_name (Name of method to wrap.):
Returns
- None
2168def apply_method_delegation_to_model(model, method_name, main_module_type): 2169 """Recursively apply method delegation to all wrapper modules with main_module in a model. 2170 2171 This traverses the entire model and adds method delegation for any module that has 2172 a main_module attribute and optionally matches specified types. 2173 2174 Args: 2175 model: The PyTorch model to traverse 2176 method_name: The method name to delegate (e.g., '_gradient_checkpointing_func') 2177 main_module_type: main_module type name to filter by. 2178 Example: 'Qwen2DecoderLayer' 2179 2180 Example: 2181 # Apply gradient checkpointing delegation to all decoder layers 2182 apply_method_delegation_to_model( 2183 model, 2184 '_gradient_checkpointing_func', 2185 main_module_type='Qwen2DecoderLayer' 2186 ) 2187 2188 Parameters 2189 ---------- 2190 model : PyTorch model. 2191 method_name : method to delegate. 2192 main_module_type : type of module that has this method. 2193 2194 Returns 2195 ------- 2196 None 2197 This function does not return a value. 2198 """ 2199 count = 0 2200 for name, module in model.named_modules(): 2201 # Check if module has main_module attribute (it's a wrapper) 2202 if hasattr(module, "main_module"): 2203 # Check if we should apply based on main_module type 2204 should_apply = True 2205 if main_module_type is not None: 2206 main_module_type_name = type(module.main_module).__name__ 2207 should_apply = main_module_type_name == main_module_type 2208 2209 if should_apply: 2210 add_method_delegation_to_module(module, method_name) 2211 count += 1 2212 2213 print(f"[PAI] Applied method delegation to {count} wrapper module instances")
Recursively apply method delegation to all wrapper modules with main_module in a model.
This traverses the entire model and adds method delegation for any module that has a main_module attribute and optionally matches specified types.
Args: model: The PyTorch model to traverse method_name: The method name to delegate (e.g., '_gradient_checkpointing_func') main_module_type: main_module type name to filter by. Example: 'Qwen2DecoderLayer'
Example: # Apply gradient checkpointing delegation to all decoder layers apply_method_delegation_to_model( model, '_gradient_checkpointing_func', main_module_type='Qwen2DecoderLayer' )
Parameters
model (PyTorch model.):
method_name (method to delegate.):
main_module_type (type of module that has this method.):
Returns
- None: This function does not return a value.
2216def make_json_serializable(obj): 2217 """Recursively convert non-JSON-serializable objects to strings. 2218 2219 Parameters 2220 ---------- 2221 obj : any 2222 The object to convert 2223 2224 Returns 2225 ------- 2226 Any 2227 JSON-serializable version of the object 2228 """ 2229 if isinstance(obj, (str, int, float, bool, type(None))): 2230 return obj 2231 elif isinstance(obj, dict): 2232 return {k: make_json_serializable(v) for k, v in obj.items()} 2233 elif isinstance(obj, (list, tuple)): 2234 return [make_json_serializable(item) for item in obj] 2235 else: 2236 # Convert non-serializable types to string 2237 return str(obj)
Recursively convert non-JSON-serializable objects to strings.
Parameters
- obj (any): The object to convert
Returns
- Any: JSON-serializable version of the object
2240def extract_gpa_config(): 2241 """Extract all configuration from GPA.pc by calling all get_* methods. 2242 2243 Returns 2244 ------- 2245 dict[str, Any] 2246 Dictionary with all GPA.pc configuration values and type metadata 2247 2248 Examples 2249 -------- 2250 >>> config = extract_gpa_config() 2251 >>> # Returns: {'max_dendrites': 10, 'device': 'cuda', '_types': {...}} 2252 2253 Parameters 2254 ---------- 2255 None 2256 2257 """ 2258 config = {} 2259 config_types = {} 2260 2261 # Get all attributes from GPA.pc 2262 for attr_name in dir(GPA.pc): 2263 # Check if it starts with 'get_' 2264 if attr_name.startswith("get_"): 2265 try: 2266 # Get the method 2267 method = getattr(GPA.pc, attr_name) 2268 2269 # Check if it's callable 2270 if callable(method): 2271 # Call it and store result with key as name without 'get_' 2272 key = attr_name[4:] # Remove 'get_' prefix 2273 value = method() 2274 2275 # Check if this is an array (has corresponding append_ method) 2276 append_method_name = f"append_{key}" 2277 is_array = hasattr(GPA.pc, append_method_name) 2278 2279 if is_array and isinstance(value, (list, tuple)): 2280 # Store array element type 2281 if len(value) > 0: 2282 element_type = type(value[0]).__name__ 2283 else: 2284 element_type = None # empty array, no conversion needed 2285 config_types[key] = { 2286 "is_array": True, 2287 "element_type": element_type, 2288 } 2289 else: 2290 # Store value type 2291 config_types[key] = { 2292 "is_array": False, 2293 "type": type(value).__name__, 2294 } 2295 2296 # Make sure value is JSON serializable 2297 config[key] = make_json_serializable(value) 2298 except Exception as e: 2299 # Skip if method fails 2300 if GPA.pc.get_verbose(): 2301 print(f"Skipping {attr_name}: {e}") 2302 continue 2303 2304 # Add types metadata to config 2305 config["_types"] = config_types 2306 2307 return config
Extract all configuration from GPA.pc by calling all get_* methods.
Returns
- dict[str, Any]: Dictionary with all GPA.pc configuration values and type metadata
Examples
>>> config = extract_gpa_config()
>>> # Returns: {'max_dendrites': 10, 'device': 'cuda', '_types': {...}}
Parameters
- None
2310def convert_to_type(value, type_name): 2311 """Convert a value to the specified type. 2312 2313 Parameters 2314 ---------- 2315 value : any 2316 The value to convert 2317 type_name : str 2318 The target type name 2319 2320 Returns 2321 ------- 2322 Any 2323 The converted value 2324 """ 2325 if type_name == "NoneType" or value is None: 2326 return None 2327 elif type_name == "bool": 2328 if isinstance(value, str): 2329 return value.lower() in ("true", "1", "yes") 2330 return bool(value) 2331 elif type_name == "int": 2332 return int(value) 2333 elif type_name == "float": 2334 return float(value) 2335 elif type_name == "str": 2336 return str(value) 2337 elif type_name == "list": 2338 if not isinstance(value, list): 2339 return [value] 2340 return value 2341 elif type_name == "dict": 2342 if not isinstance(value, dict): 2343 return {} 2344 return value 2345 elif type_name == "type": 2346 # Handle type objects - convert string representation back to type 2347 if isinstance(value, str): 2348 # Try to evaluate the type string (e.g., "<class 'torch.nn.Linear'>") 2349 # Extract the class path from the string 2350 if value.startswith("<class '") and value.endswith("'>"): 2351 class_path = value[ 2352 8:-2 2353 ] # Extract 'torch.nn.Linear' from "<class 'torch.nn.Linear'>" 2354 parts = class_path.split(".") 2355 # Try to import and get the type 2356 try: 2357 module_name = ".".join(parts[:-1]) 2358 class_name = parts[-1] 2359 module = __import__(module_name, fromlist=[class_name]) 2360 return getattr(module, class_name) 2361 except Exception as e: 2362 print( 2363 f"Warning: Could not convert type string '{value}' to actual type: {e}" 2364 ) 2365 return value 2366 return value 2367 return value 2368 elif type_name == "dtype": 2369 # Handle torch dtype objects 2370 if isinstance(value, str): 2371 # Convert string like "torch.float32" to actual dtype 2372 import torch 2373 2374 try: 2375 # Try to get the dtype from torch module 2376 if value.startswith("torch."): 2377 dtype_name = value.split(".")[ 2378 1 2379 ] # Get 'float32' from 'torch.float32' 2380 return getattr(torch, dtype_name) 2381 else: 2382 return getattr(torch, value) 2383 except Exception as e: 2384 print( 2385 f"Warning: Could not convert dtype string '{value}' to actual dtype: {e}" 2386 ) 2387 return value 2388 return value 2389 elif type_name == "device": 2390 # Handle torch device objects 2391 if isinstance(value, str): 2392 # Convert string like "cuda" or "cpu" to torch.device 2393 import torch 2394 2395 try: 2396 return torch.device(value) 2397 except Exception as e: 2398 print( 2399 f"Warning: Could not convert device string '{value}' to actual device: {e}" 2400 ) 2401 return value 2402 return value 2403 elif type_name == "builtin_function_or_method": 2404 # Handle torch functions like torch.sigmoid, torch.relu, etc. 2405 if isinstance(value, str): 2406 # Parse string like "<built-in method sigmoid of type object at 0x...>" 2407 # to extract the function name 2408 import torch 2409 2410 try: 2411 if "<built-in method " in value and " of type object" in value: 2412 # Extract function name between '<built-in method ' and ' of type object' 2413 start = value.find("<built-in method ") + len("<built-in method ") 2414 end = value.find(" of type object") 2415 func_name = value[start:end] 2416 # Try to get the function from torch module 2417 if hasattr(torch, func_name): 2418 return getattr(torch, func_name) 2419 else: 2420 print(f"Warning: torch.{func_name} not found") 2421 return value 2422 else: 2423 return value 2424 except Exception as e: 2425 print( 2426 f"Warning: Could not convert builtin function string '{value}': {e}" 2427 ) 2428 return value 2429 return value 2430 else: 2431 # Unknown type - error and debug 2432 print(f"ERROR: Unknown type '{type_name}' for value: {value}") 2433 print(f"Type of value is: {type(value).__name__}") 2434 pdb.set_trace() 2435 return value
Convert a value to the specified type.
Parameters
- value (any): The value to convert
- type_name (str): The target type name
Returns
- Any: The converted value
2438def convert_to_type_array(value, element_type): 2439 """Convert an array's elements to the specified type. 2440 2441 Parameters 2442 ---------- 2443 value : list or tuple 2444 The array to convert 2445 element_type : str or None 2446 The target type name for elements, None if array was empty 2447 2448 Returns 2449 ------- 2450 list 2451 The array with converted elements 2452 """ 2453 if not isinstance(value, (list, tuple)): 2454 return value 2455 # If element_type is None (empty array), no conversion needed 2456 if element_type is None: 2457 return list(value) if isinstance(value, tuple) else value 2458 return [convert_to_type(item, element_type) for item in value]
Convert an array's elements to the specified type.
Parameters
- value (list or tuple): The array to convert
- element_type (str or None): The target type name for elements, None if array was empty
Returns
- list: The array with converted elements
2461def set_gpa_config(config): 2462 """Set GPA.pc configuration by calling all set_* methods. 2463 2464 This is the reverse of extract_gpa_config(). It takes a configuration 2465 dictionary and calls the corresponding set_* methods on GPA.pc. 2466 Uses type metadata to ensure values are converted to the correct type. 2467 2468 Parameters 2469 ---------- 2470 config : dict 2471 Dictionary with configuration values (keys without 'set_' prefix) 2472 and optional '_types' metadata 2473 2474 Examples 2475 -------- 2476 >>> config = {'verbose': True, 'device': 'cuda'} 2477 >>> set_gpa_config(config) 2478 # Calls GPA.pc.set_verbose(True), GPA.pc.set_device('cuda'), etc. 2479 2480 Returns 2481 ------- 2482 Count of parameters that were set 2483 """ 2484 set_count = 0 2485 skip_count = 0 2486 2487 # Extract type information 2488 config_types = config.get("_types", {}) 2489 2490 for key, value in config.items(): 2491 # Skip the types metadata 2492 if key == "_types": 2493 continue 2494 2495 # Construct the set method name 2496 set_method_name = f"set_{key}" 2497 2498 # Check if the set method exists 2499 if hasattr(GPA.pc, set_method_name): 2500 try: 2501 method = getattr(GPA.pc, set_method_name) 2502 if callable(method): 2503 # Convert value to correct type if we have type info 2504 if key in config_types: 2505 type_info = config_types[key] 2506 if type_info.get("is_array", False): 2507 # Convert array elements to correct type 2508 element_type = type_info.get("element_type", "str") 2509 value = convert_to_type_array(value, element_type) 2510 else: 2511 # Convert single value to correct type 2512 value_type = type_info.get("type", "str") 2513 value = convert_to_type(value, value_type) 2514 2515 method(value) 2516 set_count += 1 2517 if GPA.pc.get_verbose(): 2518 print(f"Set {key} = {value}") 2519 except Exception as e: 2520 skip_count += 1 2521 if GPA.pc.get_verbose(): 2522 print(f"Failed to set {key}: {e}") 2523 else: 2524 skip_count += 1 2525 if GPA.pc.get_verbose(): 2526 print(f"No setter found for {key} (looking for {set_method_name})") 2527 2528 if GPA.pc.get_verbose(): 2529 print(f"Applied {set_count} PAI configuration settings ({skip_count} skipped)") 2530 2531 return set_count
Set GPA.pc configuration by calling all set_* methods.
This is the reverse of extract_gpa_config(). It takes a configuration dictionary and calls the corresponding set_* methods on GPA.pc. Uses type metadata to ensure values are converted to the correct type.
Parameters
- config (dict): Dictionary with configuration values (keys without 'set_' prefix) and optional '_types' metadata
Examples
>>> config = {'verbose': True, 'device': 'cuda'}
>>> set_gpa_config(config)
<h1 id="calls-gpapcset_verbosetrue-gpapcset_devicecuda-etc">Calls GPA.pc.set_verbose(True), GPA.pc.set_device('cuda'), etc.</h1>
Returns
- Count of parameters that were set
2742 def upload_to_huggingface(*args, **kwargs): 2743 """Raise an informative error when HuggingFace dependencies are missing. 2744 2745 Parameters 2746 ---------- 2747 *args : tuple[Any, ...] 2748 Positional arguments accepted for API compatibility. 2749 **kwargs : dict[str, Any] 2750 Keyword arguments accepted for API compatibility. 2751 2752 Returns 2753 ------- 2754 None 2755 Always raises ``ImportError``. 2756 """ 2757 raise ImportError( 2758 "huggingface_hub is required for upload_to_huggingface. " 2759 "Install it with: pip install huggingface_hub" 2760 )
Raise an informative error when HuggingFace dependencies are missing.
Parameters
- *args (tuple[Any, ...]): Positional arguments accepted for API compatibility.
- **kwargs (dict[str, Any]): Keyword arguments accepted for API compatibility.
Returns
- None: Always raises
ImportError.
2762 def from_hf_pretrained(*args, **kwargs): 2763 """Raise an informative error when HuggingFace dependencies are missing. 2764 2765 Parameters 2766 ---------- 2767 *args : tuple[Any, ...] 2768 Positional arguments accepted for API compatibility. 2769 **kwargs : dict[str, Any] 2770 Keyword arguments accepted for API compatibility. 2771 2772 Returns 2773 ------- 2774 None 2775 Always raises ``ImportError``. 2776 """ 2777 raise ImportError( 2778 "huggingface_hub is required for from_hf_pretrained. " 2779 "Install it with: pip install huggingface_hub" 2780 )
Raise an informative error when HuggingFace dependencies are missing.
Parameters
- *args (tuple[Any, ...]): Positional arguments accepted for API compatibility.
- **kwargs (dict[str, Any]): Keyword arguments accepted for API compatibility.
Returns
- None: Always raises
ImportError.