3939from typing import Literal , assert_never
4040from utils import AppearanceOptModule , CameraOptModule , knn , rgb_to_sh , set_random_seed
4141
42+ from numpy .core .multiarray import scalar as npscalar
43+ from numpy .dtypes import Float64DType as npFloat64DType
44+ from numpy import dtype as npdtype
45+
4246from gsplat import export_splats
4347from gsplat .distributed import cli
4448from gsplat .optimizers import SelectiveAdam
@@ -58,6 +62,9 @@ class Config:
5862 # Path to the .pt files to load and resume training
5963 resume_ckpt : Optional [str ] = ""
6064
65+ # Whether to retrieve optimizer state from the checkpoint
66+ retrieve_optimizer_state : bool = True
67+
6168 # Path to the dataset
6269 sfm_file : str = "sfm.json"
6370 image_alpha : bool = False
@@ -127,6 +134,9 @@ class Config:
127134 # Use random background for training to discourage transparency
128135 random_bkgd : bool = False
129136
137+ # Whether to use schedulers for the learning rates (only for MCMC)
138+ use_scheduler : bool = True
139+
130140 # LR for 3D point positions
131141 means_lr : float = 1.6e-4
132142 # LR for Gaussian scale factors
@@ -222,6 +232,7 @@ def create_splats_with_optimizers(
222232 device : str = "cuda" ,
223233 world_rank : int = 0 ,
224234 world_size : int = 1 ,
235+ strategy : Union [DefaultStrategy , MCMCStrategy ] = DefaultStrategy ,
225236) -> Tuple [torch .nn .ParameterDict , Dict [str , torch .optim .Optimizer ]]:
226237
227238 #initialize from sfm
@@ -328,6 +339,12 @@ def __init__(
328339
329340 logging .info (f"Scene scale: { self .scene_scale } " )
330341
342+ retrieve_optimizer_state = cfg .retrieve_optimizer_state and cfg .resume_ckpt != ""
343+ if retrieve_optimizer_state and isinstance (cfg .strategy , MCMCStrategy ):
344+ # In case of MCMC, the scheduler reduces the effective learning rate by 100
345+ # This line updates the learning rate accordingly
346+ cfg .means_lr = 0.01 * cfg .means_lr
347+
331348 # Model
332349 feature_dim = 32 if cfg .app_opt else None
333350 self .splats , self .optimizers = create_splats_with_optimizers (
@@ -349,6 +366,7 @@ def __init__(
349366 device = self .device ,
350367 world_rank = world_rank ,
351368 world_size = world_size ,
369+ strategy = cfg .strategy ,
352370 )
353371 logging .info (f"Model initialized. Number of GS: { len (self .splats ['means' ])} " )
354372
@@ -517,37 +535,42 @@ def train(self):
517535 #Compute effective steps
518536 max_steps = cfg .max_epochs * len (self .trainset )
519537
520- schedulers = [
521- # means has a learning rate schedule, that end at 0.01 of the initial value
522- torch .optim .lr_scheduler .ExponentialLR (
523- self .optimizers ["means" ], gamma = 0.01 ** (1.0 / max_steps )
524- ),
525- ]
538+ schedulers = []
539+ if isinstance (cfg .strategy , MCMCStrategy ) and cfg .use_scheduler :
526540
527- if cfg .pose_opt :
528- # pose optimization has a learning rate schedule
529- schedulers .append (
541+ logging .info ("Create schedulers" )
542+
543+ schedulers = [
544+ # means has a learning rate schedule that ends at 0.01 of the initial value
530545 torch .optim .lr_scheduler .ExponentialLR (
531- self .pose_optimizers [0 ], gamma = 0.01 ** (1.0 / max_steps )
546+ self .optimizers ["means" ], gamma = 0.01 ** (1.0 / max_steps )
547+ ),
548+ ]
549+
550+ if cfg .pose_opt :
551+ # pose optimization has a learning rate schedule
552+ schedulers .append (
553+ torch .optim .lr_scheduler .ExponentialLR (
554+ self .pose_optimizers [0 ], gamma = 0.01 ** (1.0 / max_steps )
555+ )
532556 )
533- )
534557
535- if cfg .use_bilateral_grid :
536- # bilateral grid has a learning rate schedule. Linear warmup for 1000 steps.
537- schedulers .append (
538- torch .optim .lr_scheduler .ChainedScheduler (
539- [
540- torch .optim .lr_scheduler .LinearLR (
541- self .bil_grid_optimizers [0 ],
542- start_factor = 0.01 ,
543- total_iters = 1000 ,
544- ),
545- torch .optim .lr_scheduler .ExponentialLR (
546- self .bil_grid_optimizers [0 ], gamma = 0.01 ** (1.0 / max_steps )
547- ),
548- ]
558+ if cfg .use_bilateral_grid :
559+ # bilateral grid has a learning rate schedule. Linear warmup for 1000 steps.
560+ schedulers .append (
561+ torch .optim .lr_scheduler .ChainedScheduler (
562+ [
563+ torch .optim .lr_scheduler .LinearLR (
564+ self .bil_grid_optimizers [0 ],
565+ start_factor = 0.01 ,
566+ total_iters = 1000 ,
567+ ),
568+ torch .optim .lr_scheduler .ExponentialLR (
569+ self .bil_grid_optimizers [0 ], gamma = 0.01 ** (1.0 / max_steps )
570+ ),
571+ ]
572+ )
549573 )
550- )
551574
552575 trainloader = torch .utils .data .DataLoader (
553576 self .trainset ,
@@ -560,6 +583,34 @@ def train(self):
560583
561584 step = - 1
562585
586+ if cfg .resume_ckpt != "" :
587+ torch .serialization .add_safe_globals ([npscalar , npdtype , npFloat64DType ])
588+ logging .info ("Loading Gaussians from checkpoint" )
589+ ckpt = torch .load (cfg .resume_ckpt , map_location = device , weights_only = True )
590+ for k in self .splats .keys ():
591+ self .splats [k ].data = torch .cat ([ckpt ["splats" ][k ]])
592+ if cfg .pose_opt and "pose_adjust" in ckpt :
593+ pose_adjust = self .pose_adjust .module if world_size > 1 else self .pose_adjust
594+ pose_adjust .load_state_dict (ckpt ["pose_adjust" ])
595+ if cfg .app_opt and "app_module" in ckpt :
596+ app_module = self .app_module .module if world_size > 1 else self .app_module
597+ app_module .load_state_dict (ckpt ["app_module" ])
598+ if cfg .retrieve_optimizer_state :
599+ logging .info ("Loading optimizer state from checkpoint" )
600+ self .strategy_state = ckpt ["strategy_state" ]
601+ for name , opt_state_dict in ckpt ["optimizer_state_dicts" ].items ():
602+ self .optimizers [name ].load_state_dict (opt_state_dict )
603+ if cfg .pose_opt and "pose_optimizer_state_dicts" in ckpt :
604+ for optimizer , opt_state_dict in zip (self .pose_optimizers , ckpt ["pose_optimizer_state_dicts" ]):
605+ optimizer .load_state_dict (opt_state_dict )
606+ if cfg .app_opt and "app_optimizer_state_dicts" in ckpt :
607+ for optimizer , opt_state_dict in zip (self .app_optimizers , ckpt ["app_optimizer_state_dicts" ]):
608+ optimizer .load_state_dict (opt_state_dict )
609+ if cfg .use_bilateral_grid and "bil_grid_optimizer_state_dicts" in ckpt :
610+ for optimizer , opt_state_dict in zip (self .bil_grid_optimizers , ckpt ["bil_grid_optimizer_state_dicts" ]):
611+ optimizer .load_state_dict (opt_state_dict )
612+ step = ckpt ["step" ] - 1
613+
563614 if not cfg .use_progress_bar :
564615 logging .info ("Training started" )
565616
@@ -746,13 +797,14 @@ def train(self):
746797 packed = cfg .packed ,
747798 )
748799 elif isinstance (self .cfg .strategy , MCMCStrategy ):
800+ means_lr = schedulers [0 ].get_last_lr ()[0 ] if cfg .use_scheduler else self .optimizers ["means" ].param_groups [0 ]["lr" ]
749801 self .cfg .strategy .step_post_backward (
750802 params = self .splats ,
751803 optimizers = self .optimizers ,
752804 state = self .strategy_state ,
753805 step = step ,
754806 info = info ,
755- lr = schedulers [ 0 ]. get_last_lr ()[ 0 ] ,
807+ lr = means_lr ,
756808 )
757809 else :
758810 assert_never (self .cfg .strategy )
@@ -763,7 +815,16 @@ def train(self):
763815 if epoch in [i - 1 for i in cfg .save_epochs ] or epoch == cfg .max_epochs - 1 :
764816
765817 path = f"{ self .ckpt_dir } /ckpt_{ epoch } _rank{ self .world_rank } .pt"
766- data = {"step" : step , "splats" : self .splats .state_dict ()}
818+ data = {"step" : step ,
819+ "splats" : self .splats .state_dict (),
820+ "strategy_state" : self .strategy_state ,
821+ "optimizer_state_dicts" : {name : opt .state_dict () for name , opt in self .optimizers .items ()}}
822+ if cfg .pose_opt :
823+ data ["pose_optimizer_state_dicts" ] = [optimizer .state_dict () for optimizer in self .pose_optimizers ]
824+ if cfg .app_opt :
825+ data ["app_optimizer_state_dicts" ] = [optimizer .state_dict () for optimizer in self .app_optimizers ]
826+ if cfg .use_bilateral_grid :
827+ data ["bil_grid_optimizer_state_dicts" ] = [optimizer .state_dict () for optimizer in self .bil_grid_optimizers ]
767828
768829 if cfg .pose_opt :
769830 if world_size > 1 :
@@ -784,13 +845,6 @@ def main(local_rank: int, world_rank, world_size: int, cfg: Config):
784845 #Create object for computation
785846 runner = Runner (local_rank , world_rank , world_size , cfg )
786847
787- #if a checkpoint was passed, will load the gaussians from there
788- if cfg .resume_ckpt != "" :
789- logging .info ("Loading from checkpoint" )
790- ckpt = torch .load (cfg .resume_ckpt , map_location = runner .device , weights_only = True )
791- for k in runner .splats .keys ():
792- runner .splats [k ].data = torch .cat ([ckpt ["splats" ][k ]])
793-
794848 #Launch training
795849 runner .train ()
796850
0 commit comments