@@ -2111,6 +2111,35 @@ def load_env(env_name, args):
21112111 return pufferlib .vector .make (make_env , env_kwargs = args ["env" ], ** args ["vec" ])
21122112
21132113
2114+ def _load_and_verify_checkpoint (policy , ckpt_path , device , source ):
2115+ """Load `ckpt_path` into `policy` strictly, then verify every tensor in
2116+ the checkpoint was copied in exactly. Raises on mismatch; logs a single-
2117+ line confirmation on success so finetune runs can prove which weights
2118+ they started from.
2119+ """
2120+ state_dict = torch .load (ckpt_path , map_location = device )
2121+ state_dict = {k .replace ("module." , "" ): v for k , v in state_dict .items ()}
2122+
2123+ # strict=True: raises RuntimeError on any missing or unexpected key.
2124+ policy .load_state_dict (state_dict , strict = True )
2125+
2126+ policy_sd = policy .state_dict ()
2127+ mismatched = [k for k , v in state_dict .items () if not torch .equal (policy_sd [k ], v .to (policy_sd [k ].device ))]
2128+ if mismatched :
2129+ raise RuntimeError (
2130+ f"[load] { len (mismatched )} /{ len (state_dict )} tensors did not match after load from { ckpt_path } : "
2131+ f"{ mismatched [:5 ]} { '...' if len (mismatched ) > 5 else '' } "
2132+ )
2133+
2134+ total_params = sum (v .numel () for v in state_dict .values ())
2135+ # Content fingerprint: tiny but stable across machines for the same .pt.
2136+ fingerprint = sum (float (v .detach ().to (torch .float64 ).sum ().item ()) for v in state_dict .values ())
2137+ print (
2138+ f"[load] { source } : loaded { len (state_dict )} tensors ({ total_params :,} params) "
2139+ f"from { ckpt_path } ; all values verified (sum-fingerprint={ fingerprint :.6e} )."
2140+ )
2141+
2142+
21142143def load_policy (args , vecenv , env_name = "" ):
21152144 package = args ["package" ]
21162145 module_name = "pufferlib.ocean" if package == "ocean" else f"pufferlib.environments.{ package } "
@@ -2136,16 +2165,14 @@ def load_policy(args, vecenv, env_name=""):
21362165 else :
21372166 raise pufferlib .APIUsageError ("No run id provided for eval" )
21382167
2139- state_dict = torch .load (path , map_location = device )
2140- policy .load_state_dict (clean_policy_state_dict (state_dict ))
2168+ _load_and_verify_checkpoint (policy , path , device , source = f"load_id={ load_id } " )
21412169
21422170 load_path = args ["load_model_path" ]
21432171 if load_path == "latest" :
21442172 load_path = max (glob .glob (f"experiments/{ env_name } *.pt" ), key = os .path .getctime )
21452173
21462174 if load_path is not None :
2147- state_dict = torch .load (load_path , map_location = device )
2148- policy .load_state_dict (clean_policy_state_dict (state_dict ))
2175+ _load_and_verify_checkpoint (policy , load_path , device , source = "load_model_path" )
21492176 # state_path = os.path.join(*load_path.split('/')[:-1], 'state.pt')
21502177 # optim_state = torch.load(state_path)['optimizer_state_dict']
21512178 # pufferl.optimizer.load_state_dict(optim_state)
0 commit comments