"""Complete joint epochs, exact-resume checkpoints, W&B and verified Hub backups.""" from pathlib import Path import sys sys.path.insert(0,str(Path(__file__).resolve().parent)) from multitask_model import * import argparse,ast,copy,math,random os.environ.setdefault('CUBLAS_WORKSPACE_CONFIG',':4096:8') def rng_state(): return dict(torch_rng=torch.get_rng_state(),cuda_rng=torch.cuda.get_rng_state_all(), numpy_rng=np.random.get_state(),python_rng=random.getstate()) def restore_rng(state): torch.set_rng_state(state['torch_rng']);torch.cuda.set_rng_state_all(state['cuda_rng']) np.random.set_state(state['numpy_rng']);random.setstate(state['python_rng']) def check_monroe_losses(): """Compare losses and gradients with the pinned author's actual implementation.""" import openadmet_evaluate as runtime runtime.restore_monroe() from monroe.mtl.UW import UW source=WORK/'external/Monroe/monroe/train/loss.py' tree=ast.parse(source.read_text());function=next(n for n in tree.body if isinstance(n,ast.FunctionDef) and n.name=='off_diag_cov_loss') namespace={'torch':torch};exec(compile(ast.Module(body=[function],type_ignores=[]),str(source),'exec'),namespace) torch.manual_seed(42026) z=torch.randn(19,37,requires_grad=True);z2=z.detach().clone().requires_grad_() ref_cov=namespace['off_diag_cov_loss'](z);our_cov=off_diag_cov_loss(z2) ref_cov.backward();our_cov.backward() assert torch.equal(ref_cov,our_cov) and torch.equal(z.grad,z2.grad) reference=UW(task_types={k:None for k in CONFIG['task_order']},encoder_class=None,decoders=nn.Identity(),device='cpu') reference.init_param() losses=torch.tensor([.2,.8,1.3],requires_grad=True) ours=losses.detach().clone().requires_grad_();scale=nn.Parameter(reference.loss_scale.detach().clone()) ref=reference.backward(losses,extra_losses=torch.tensor([.125])) value=uncertainty_objective(ours,scale)+.125;value.backward() assert torch.equal(ref,value) and torch.equal(reference.loss_scale.grad,scale.grad) and torch.equal(losses.grad,ours.grad) result=dict(passed=True,monroe_revision=MONROE_REV,uw_value=float(value.detach()), uw_value_and_gradients_exact=True,covariance_value_and_gradients_exact=True, uw_source_sha256=sha(WORK/'external/Monroe/monroe/mtl/UW.py'),covariance_source_sha256=sha(source), no_normalization_of_objective_weights=True,decorrelation_outside_uncertainty=True, decorrelation_weight=CONFIG['decorrelation_weight']) write(OUT/'loss_implementation_validation.json',result) return result class TrainingData: def __init__(self): self.cosmo=cosmo_frame();self.pairs=pd.read_parquet(DATA/'combisolv_pairs.parquet') self.c_cache=RawCache('cosmo');self.q_cache=RawCache('combisolv') self.tr=np.flatnonzero(self.cosmo.joint_split.to_numpy()=='train');self.va=np.flatnonzero(self.cosmo.joint_split.to_numpy()=='validation') self.qtr=np.flatnonzero(self.pairs.split.to_numpy()=='train');self.qva=np.flatnonzero(self.pairs.split.to_numpy()=='validation') transform=json.loads((DATA/'cosmo_label_transform.json').read_text()) self.c_mean=np.asarray(transform['mean']);self.c_scale=np.asarray(transform['scale']) labels=self.cosmo[[f'p_{i:03d}' for i in range(153)]+['area_A2','volume_A3']].to_numpy(dtype=np.float64) loglabels=np.c_[np.log1p(labels[:,:153]),np.log(labels[:,153:])] self.c_target=torch.tensor((loglabels-self.c_mean)/self.c_scale,dtype=torch.float32,device='cuda') self.q_transform=json.loads((DATA/'combisolv_label_transform.json').read_text()) self.q_target=torch.tensor((self.pairs.dGsolv_kcal_mol.to_numpy()-self.q_transform['mean'])/self.q_transform['scale'],dtype=torch.float32,device='cuda') assert len(self.c_cache.lengths)==len(self.cosmo) and len(self.q_target)==1000000 assert torch.isfinite(self.c_target).all() and torch.isfinite(self.q_target).all() def plans(self,epoch): rng=np.random.default_rng(CONFIG['seed']+epoch) cp=list(batches(self.tr,self.c_cache.lengths,rng,max_size=CONFIG['cosmo_batch_size'],atom_cap=CONFIG['cosmo_atom_cap'])) order=rng.permutation(self.qtr) qp=[order[i:i+CONFIG['qm_pair_batch_size']] for i in range(0,len(order),CONFIG['qm_pair_batch_size'])] assert sorted(i for batch in cp for i in batch)==self.tr.tolist() assert np.array_equal(np.sort(np.concatenate(qp)),self.qtr) return cp,qp,max(len(cp),len(qp)) def update(model,optimizer,data,ci,qi): optimizer.zero_grad(set_to_none=True) losses,cov,z,_,parts=model.cosmo_denoising(data.c_cache.sample(ci),data.c_target[ci]) first=uncertainty_objective(losses,model.log_variance[:2])+CONFIG['decorrelation_weight']*.5*cov # Accumulate both objective graphs before a single optimizer step. This is algebraically # the full three-task UW sum and average of the two covariance penalties. first.backward() log=dict(denoising=float(losses[0].detach()),denoising_raw=float(losses[0].detach())*1088, cosmo=float(losses[1].detach()),cov_cosmo=float(cov.detach()), descriptor_std_cosmo=float(z.detach().float().std(dim=0).mean()),**{'cosmo_'+k:v for k,v in parts.items()}) first_value=float(first.detach()) del losses,cov,z,first qm,cov,pred,z=model.qm(data.q_cache,qi,data.pairs,data.q_target) second=uncertainty_objective(qm[None],model.log_variance[2:])+CONFIG['decorrelation_weight']*.5*cov second.backward() log.update(combisolv=float(qm.detach()),cov_combisolv=float(cov.detach()), descriptor_std_combisolv=float(z.detach().float().std(dim=0).mean()), total=first_value+float(second.detach())) norm=nn.utils.clip_grad_norm_(model.parameters(),CONFIG['gradient_clip'],error_if_nonfinite=True) assert all(math.isfinite(v) for v in log.values()),log optimizer.step() log['gradient_norm']=float(norm) log['combisolv_rmse_kcal_mol']=math.sqrt(log['combisolv'])*data.q_transform['scale'] for i,task in enumerate(CONFIG['task_order']): log['log_variance_'+task]=float(model.log_variance[i].detach()) log['precision_'+task]=float((1/(2*model.log_variance[i].exp())).detach()) return log @torch.no_grad() def validate(model,data): old_rng=rng_state();torch.manual_seed(83026);torch.cuda.manual_seed_all(83026) model.eval();sums=np.zeros(3);count=0 for ids in batches(data.va,data.c_cache.lengths,max_size=64,atom_cap=3600): losses,cov,_,_,_=model.cosmo_denoising(data.c_cache.sample(ids),data.c_target[ids]) sums+=np.asarray([float(losses[0]),float(losses[1]),float(cov)])*len(ids);count+=len(ids) assert count==len(data.va) q_sse=0.;q_mae=0.;q_count=0 for start in range(0,len(data.qva),CONFIG['qm_pair_batch_size']): ids=data.qva[start:start+CONFIG['qm_pair_batch_size']] loss,_,pred,_=model.qm(data.q_cache,ids,data.pairs,data.q_target) diff=(pred-data.q_target[ids])*data.q_transform['scale'] q_sse+=float(diff.double().square().sum());q_mae+=float(diff.double().abs().sum());q_count+=len(ids) assert q_count==len(data.qva) restore_rng(old_rng) result=dict(denoising=float(sums[0]/count),denoising_raw=float(sums[0]/count*1088), cosmo=float(sums[1]/count),cov_cosmo=float(sums[2]/count), combisolv_rmse_kcal_mol=math.sqrt(q_sse/q_count),combisolv_mae_kcal_mol=q_mae/q_count, cosmo_rows=count,combisolv_rows=q_count) assert all(math.isfinite(v) for v in result.values()),result return result def save_checkpoint(path,model,optimizer,scheduler,epoch,global_step,metrics): pending=path.with_name(path.name+'.partial');pending.mkdir(parents=True,exist_ok=True) for name,module in model.parts.items():torch.save(module.state_dict(),pending/name) torch.save(dict(log_variance=model.log_variance.detach().cpu(),task_order=CONFIG['task_order']),pending/'task_weighting.pth') torch.save(dict(epoch=epoch,global_step=global_step,optimizer=optimizer.state_dict(),scheduler=scheduler.state_dict(), config=CONFIG,dataset=json.loads((OUT/'prepared_data_hub.json').read_text()),**rng_state()),pending/'training_state.pt') write(pending/'metrics.json',metrics);write(pending/'config.json',CONFIG) for filename in ['cosmo_label_transform.json','combisolv_label_transform.json','manifest.json']: shutil.copyfile(DATA/filename,pending/('data_manifest.json' if filename=='manifest.json' else filename)) shutil.copyfile(OUT/'initialization.json',pending/'initialization.json') (pending/'post_training_architecture_config.yaml').write_text((common.ORIGINAL/'post_training_architecture_config.yaml').read_text()) write(pending/'manifest.json',dict(epoch=epoch,global_step=global_step, files={p.name:dict(bytes=p.stat().st_size,sha256=sha(p)) for p in pending.iterdir() if p.is_file() and p.name!='manifest.json'})) if path.exists():shutil.rmtree(path) pending.rename(path) def upload_checkpoint(path): marker=path/'hub_verified.json' if marker.exists():return json.loads(marker.read_text()) api=HfApi();api.create_repo(MODEL_REPO,repo_type='model',exist_ok=True) files=[p for p in path.iterdir() if p.is_file() and p.name!='hub_verified.json'] for attempt in range(5): try: commit=api.create_commit(repo_id=MODEL_REPO, operations=[CommitOperationAdd(path_in_repo=path.name+'/'+p.name,path_or_fileobj=str(p)) for p in files], commit_message=f'Preserve complete joint checkpoint {path.name}: all heads, learned task weights and exact resume state') entries={s.rfilename:s for s in api.model_info(MODEL_REPO,revision=commit.oid,files_metadata=True).siblings} for p in files: if p.suffix not in ['.pth','.pt']:continue remote=entries[path.name+'/'+p.name] assert remote.lfs and remote.lfs.sha256==sha(p),p.name result=dict(repository=MODEL_REPO,revision=commit.oid,epoch=int(path.name.split('_')[-1]),verified=True) write(marker,result);return result except Exception: if attempt==4:raise print('HUB_UPLOAD_RETRY',traceback.format_exc(),flush=True);time.sleep(min(10*2**attempt,50)) def smoke(data): verified_path=OUT/'loss_implementation_validation.json' if verified_path.exists(): verified=json.loads(verified_path.read_text()) assert verified['passed'] assert verified['uw_source_sha256']==sha(WORK/'external/Monroe/monroe/mtl/UW.py') assert verified['covariance_source_sha256']==sha(WORK/'external/Monroe/monroe/train/loss.py') else:check_monroe_losses() model=MultiTask();optimizer=optimizer_for(model) scheduler=torch.optim.lr_scheduler.LambdaLR(optimizer,lambda e:1.) model.eval();baseline=RawCache('bigsoldb') with torch.no_grad(): ref=np.load(WORK/'bigsoldb_evaluation/features/cosmo_epoch10.npz')['X'] ours=np.empty_like(ref) for ids in common.batch_indices(np.arange(len(ref)),baseline.lengths,max_size=24,atom_cap=2400): _,_,z=model.encode(baseline.sample(ids));ours[ids]=z.cpu().numpy() difference=np.abs(ours-ref) assert np.allclose(ours,ref,atol=2e-4,rtol=2e-4),(float(difference.max()),float(difference.mean())) initial_difference=dict(molecules=len(ref),max_absolute=float(difference.max()),mean_absolute=float(difference.mean())) frozen={n:b.cpu().clone() for n,b in model.pre.atomic_preprocessor.invariant_normalization.named_buffers()} cp,qp,_=data.plans(1);ci=cp[10];qi=qp[10] initial=next(model.encoder.parameters()).detach().clone() model.train();torch.cuda.reset_peak_memory_stats();t=time.perf_counter() first=update(model,optimizer,data,ci,qi);duration=time.perf_counter()-t assert not torch.equal(initial,next(model.encoder.parameters()).detach()) gradients={name:any(p.grad is not None and bool((p.grad!=0).any()) for p in module.parameters()) for name,module in model.parts.items()} assert all(gradients.values()),gradients assert model.log_variance.grad is not None and bool((model.log_variance.grad!=0).all()) temp=OUT/'smoke_checkpoint';save_checkpoint(temp,model,optimizer,scheduler,0,1,first) expected=update(model,optimizer,data,ci,qi) expected_state={n:v.detach().cpu().clone() for n,v in model.state_dict().items()} model.load_parts(temp);state=torch.load(temp/'training_state.pt',map_location='cpu',weights_only=False) optimizer.load_state_dict(state['optimizer']);scheduler.load_state_dict(state['scheduler']);restore_rng(state) replay_started=time.perf_counter() actual=update(model,optimizer,data,ci,qi) steady_duration=time.perf_counter()-replay_started assert all(np.isclose(actual[k],expected[k],atol=1e-7,rtol=1e-7) for k in expected),(expected,actual) assert all(torch.equal(v.detach().cpu(),expected_state[n]) for n,v in model.state_dict().items()),'Resume changed next-step parameters' for n,b in model.pre.atomic_preprocessor.invariant_normalization.named_buffers():assert torch.equal(b.cpu(),frozen[n]) peak=torch.cuda.max_memory_allocated()/1e9 result=dict(passed=True,all_head_gradients=gradients,encoder_updated=True,task_weights_updated=True, optimizer_and_rng_resume_next_step_exact=True,normalization_buffers_unchanged=True, initial_bigsoldb_embedding_match=initial_difference,real_batch_loss=first,seconds_per_joint_step=duration, steady_seconds_per_joint_step=steady_duration, smoke_cosmo_molecules=len(ci),smoke_qm_pairs=len(qi),peak_gpu_GB=peak,config=CONFIG) write(OUT/'smoke_validation.json',result);shutil.rmtree(temp) status('smoke_passed',seconds_per_joint_step=duration,peak_gpu_GB=peak,initial_embedding_difference=initial_difference) def main(): parser=argparse.ArgumentParser();parser.add_argument('--smoke',action='store_true');parser.add_argument('--check-losses',action='store_true');args=parser.parse_args() OUT.mkdir(exist_ok=True) lock=(OUT/'training.lock').open('a');fcntl.flock(lock,fcntl.LOCK_EX|fcntl.LOCK_NB) if args.check_losses:print(check_monroe_losses());return assert (OUT/'preparation_complete.json').exists(),'Data preparation has not finished' # Ensure repeated molecule-index gradients and CUDA GEMMs replay exactly. torch.use_deterministic_algorithms(True) torch.manual_seed(CONFIG['seed']);torch.cuda.manual_seed_all(CONFIG['seed']);np.random.seed(CONFIG['seed']);random.seed(CONFIG['seed']) data=TrainingData() if args.smoke:smoke(data);return smoke_result=json.loads((OUT/'smoke_validation.json').read_text()) assert smoke_result['passed'] and smoke_result['config']==CONFIG checkpoints=sorted((OUT/'checkpoints').glob('epoch_[0-9][0-9]')) if (OUT/'checkpoints').exists() else [] model=MultiTask(checkpoints[-1] if checkpoints else None);optimizer=optimizer_for(model) scheduler=torch.optim.lr_scheduler.LambdaLR(optimizer,lambda e:.1+.9*.5*(1+math.cos(math.pi*e/CONFIG['epochs']))) frozen={n:b.cpu().clone() for n,b in model.pre.atomic_preprocessor.invariant_normalization.named_buffers()} start=1;global_step=0 if checkpoints: state=torch.load(checkpoints[-1]/'training_state.pt',map_location='cpu',weights_only=False) assert state['config']==CONFIG assert state['dataset']==json.loads((OUT/'prepared_data_hub.json').read_text()) optimizer.load_state_dict(state['optimizer']);scheduler.load_state_dict(state['scheduler']);restore_rng(state) start=state['epoch']+1;global_step=state['global_step'];upload_checkpoint(checkpoints[-1]) write(OUT/'resume_state.json',dict(restored_epoch=start-1,optimizer_scheduler_rng_restored=True,global_step=global_step)) import wandb wb_path=OUT/'wandb.json';wb=json.loads(wb_path.read_text()) if wb_path.exists() else dict(run_id=wandb.util.generate_id()) write(wb_path,wb) run=wandb.init(entity='queimo',project='rem3di-reproduction',id=wb['run_id'],resume='allow',mode='online', name='pcqm2-cosmo10-combisolv-three-task-5epochs',group='rem3di-cosmo-combisolv',job_type='multitask-pretraining', config={**CONFIG,'dataset':json.loads((DATA/'manifest.json').read_text()),'initialization':json.loads((OUT/'initialization.json').read_text())}, dir=str(OUT),settings=wandb.Settings(init_timeout=90)) write(wb_path,dict(run_id=run.id,url=run.url));write(OUT/'config.json',CONFIG) run.define_metric('global_step');run.define_metric('train/*',step_metric='global_step') run.define_metric('epoch');run.define_metric('validation/*',step_metric='epoch') if checkpoints:restore_rng(state) try: for epoch in range(start,CONFIG['epochs']+1): model.train();cp,qp,total=data.plans(epoch);started=time.time();history=[];seen_c=seen_q=0 status('training',epoch=epoch,epochs=CONFIG['epochs'],batches=total,cosmo_batches=len(cp),qm_batches=len(qp),wandb_url=run.url) for bi in range(total): ci=cp[bi%len(cp)];qi=qp[bi%len(qp)] log=update(model,optimizer,data,ci,qi);history.append(log);global_step+=1;seen_c+=len(ci);seen_q+=len(qi) if bi==0 or (bi+1)%20==0 or bi+1==total: run.log(dict(global_step=global_step,epoch=epoch,**{'train/'+k:v for k,v in log.items()})) status('training',epoch=epoch,epochs=CONFIG['epochs'],batch=bi+1,batches=total,global_step=global_step, cosmo_molecules_seen=seen_c,combisolv_pairs_seen=seen_q,seconds=time.time()-started, losses={k:log[k] for k in ['denoising','cosmo','combisolv','total']},wandb_url=run.url) assert seen_c>=len(data.tr) and seen_q>=len(data.qtr) status('validating',epoch=epoch,wandb_url=run.url) validation=validate(model,data) for n,b in model.pre.atomic_preprocessor.invariant_normalization.named_buffers():assert torch.equal(b.cpu(),frozen[n]) scheduler.step() result=dict(epoch=epoch,global_step=global_step,seconds=time.time()-started, train={k:float(np.mean([h[k] for h in history])) for k in history[0]},validation=validation, coverage=dict(cosmo_unique_rows=len(data.tr),combisolv_unique_rows=len(data.qtr), cosmo_rows_with_repeats=seen_c,combisolv_rows_with_repeats=seen_q,every_training_row_covered=True), task_log_variances=model.log_variance.detach().cpu().tolist()) target=OUT/'checkpoints'/f'epoch_{epoch:02d}' save_checkpoint(target,model,optimizer,scheduler,epoch,global_step,result) status('uploading_checkpoint',epoch=epoch,wandb_url=run.url) hub=upload_checkpoint(target) write(OUT/'latest_epoch.json',dict(**result,hub=hub,wandb_url=run.url)) run.log(dict(epoch=epoch,**{'validation/'+k:v for k,v in validation.items()}, **{'epoch/'+k:v for k,v in result['train'].items()},**{'backup/verified_epoch':epoch,'backup/revision':hub['revision']})) # Run frozen Kumo in a separate process after releasing all training GPU tensors. # The driver performs all requested checkpoint probes after training finishes. write(OUT/'training_complete.json',dict(epochs=CONFIG['epochs'],wandb_url=run.url,completed_unix=time.time())) run.summary.update(dict(training_complete=True,epochs=CONFIG['epochs']));run.finish() status('training_complete',epochs=CONFIG['epochs']) except Exception: status('failed_training',error=traceback.format_exc());run.finish(exit_code=1);raise if __name__=='__main__': try:main() except Exception:status('failed_training',error=traceback.format_exc());raise