rem3di-scratch-pcqm-cosmo / source /scratch_model.py
Queimo's picture
Verify random initialization, real-data gradients and exact next-step resume before scratch training
04a3ade verified
Raw History Blame Contribute Delete
6.15 kB
"""Random-initialized author architecture, embedding denoising and COSMO heads."""
from pathlib import Path
import sys
sys.path.insert(0,str(Path(__file__).resolve().parent))
from scratch_data import *
from torch import nn
import yaml,math
from threedscriptors.configuration.architecture_config import EncoderDecoderArchitectureConfig
from threedscriptors.data_handling.sample import PreprocessedSample
from threedscriptors.training.noise_scheduler import NoiseModule,ConstantSchedule
from threedscriptors.training.pretraining import atom_denoising_loss
from train_cosmo import loss_terms,batches
CONFIG=dict(epochs=10,probes_per_epoch=5,seed=42026,architecture_source_commit='20df6fe9c61154e351d677cb0f77fd716d00beae',
initialization='Random learned Rem3Di weights; no published or PCQM/COSMO checkpoint loaded',
learning_rate_max=1e-4,weight_decay=1e-3,learning_rate_uncertainty=1e-3,gradient_clip=1.,
noise_sigma=.3,denoising='Exact author atomic embedding denoising loss and gradient paths, on PCQM',
uncertainty='sum(L_t/(2*exp(s_t))+s_t/2); raw denoising loss; initial log variance ln(1088)-0.5 for denoising, -0.5 for COSMO',
uncertainty_initialization='Equivalent initial precision to author-denoising per channel; no modification of raw denoising function',
task_order=['rem3di_denoising','cosmo'],cosmo_head='640 -> 256 SiLU -> 155',
pcqm_batch_size=128,pcqm_atom_cap=5250,cosmo_batch_size=96,cosmo_atom_cap=4000,
epoch_definition='Maximum of complete PCQM and COSMO loader lengths; shorter loader cycles; all retained training molecules seen each epoch',
scheduler='OneCycleLR max_lr=1e-4, total optimizer steps computed from ten exact deterministic epoch plans',
decorrelation=False,combisolv=False,mace='Frozen MACE-POLAR-1-M; raw 4096-channel float32 cache',
normalization='Fresh train-only population invariant mean/std across PCQM and COSMO atoms',
probe='Frozen pooled 640; solute640+solvent640+Temperature_K; exact BigSolDB rows; full75731-row Kumo Large context, 4 estimators, seed42',
probe_schedule='ceil(k * epoch_steps / 5), k=1..5, each epoch; fifty test probes; no test-based selection',
checkpoint_policy='All fifty probe points include all weights, optimizer, scheduler and RNG; verified Hugging Face backup before probing')
def uncertainty(losses,log_variance):
return (losses/(2*log_variance.exp())+log_variance/2).sum()
class ScratchJoint(nn.Module):
def __init__(self,checkpoint=None,device='cuda'):
super().__init__()
checkpoint=Path(checkpoint) if checkpoint is not None else None
architecture_path=checkpoint/'post_training_architecture_config.yaml' if checkpoint is not None else OUT/'architecture.yaml'
architecture=yaml.safe_load(architecture_path.read_text())
architecture['mace_config']=None
cfg=EncoderDecoderArchitectureConfig.model_validate(architecture)
# Constructors initialize every learned tensor. Only data statistics are supplied.
stats_path=checkpoint/'normalization.pt' if checkpoint is not None else DATA/'normalization.pt'
stats=torch.load(stats_path,map_location='cpu',weights_only=True)
self.pre=cfg._build_preprocessor()
self.pre.atomic_preprocessor.double();self.pre.geometric_preprocessor.float()
self.pre.atomic_preprocessor.invariant_normalization.set_stats(stats['mean'],stats['std'],dtype=torch.float64)
self.encoder=cfg._build_encoder().float()
self.denoiser=cfg.decoder_config.build().float()
self.cosmo_head=nn.Sequential(nn.Linear(640,256),nn.SiLU(),nn.Linear(256,155))
self.log_variance=nn.Parameter(torch.tensor([math.log(1088)-.5,-.5],dtype=torch.float32))
self.noise=NoiseModule(ConstantSchedule(.3))
if checkpoint is not None:self.load_parts(Path(checkpoint))
self.to(device)
@property
def parts(self):
return {'encoder.pth':self.encoder,'atomic_preprocessor.pth':self.pre.atomic_preprocessor,
'geometric_preprocessor.pth':self.pre.geometric_preprocessor,'denoising_decoder.pth':self.denoiser,
'cosmo_head.pth':self.cosmo_head}
def load_parts(self,path):
manifest=json.loads((path/'manifest.json').read_text())
for name,module in self.parts.items():
assert sha(path/name)==manifest['files'][name]['sha256'],name
module.load_state_dict(torch.load(path/name,map_location='cpu',weights_only=True),strict=True)
assert sha(path/'task_weighting.pth')==manifest['files']['task_weighting.pth']['sha256']
state=torch.load(path/'task_weighting.pth',map_location='cpu',weights_only=True)
with torch.no_grad():self.log_variance.copy_(state['log_variance'])
def encode(self,sample):
clean=self.pre(sample);descriptor=self.encoder(clean)
z=descriptor if torch.is_tensor(descriptor) else descriptor.flat
return clean,descriptor,z
def denoising(self,sample):
clean,descriptor,z=self.encode(sample)
target=clean.preprocessed_atomic_embeddings.clone()
noisy=self.noise(target,clean.padding_mask)
corrupted=PreprocessedSample(preprocessed_atomic_embeddings=noisy,
initial_pair_representation=clean.initial_pair_representation.detach(),
geometrical_encoding=clean.geometrical_encoding.detach(),
padding_mask=clean.padding_mask,pair_mask=clean.pair_mask)
reconstruction=self.denoiser(corrupted,descriptor)
loss=atom_denoising_loss(target,reconstruction,clean.padding_mask,.3)
return loss,z,target
def cosmo(self,sample,target):
_,_,z=self.encode(sample);pred=self.cosmo_head(z)
loss,parts=loss_terms(pred,target)
return loss,z,pred,parts
def optimizer_for(model):
return torch.optim.AdamW([
dict(params=list(model.pre.parameters())+list(model.encoder.parameters())+list(model.denoiser.parameters())+list(model.cosmo_head.parameters()),lr=CONFIG['learning_rate_max']),
dict(params=[model.log_variance],lr=CONFIG['learning_rate_uncertainty'],weight_decay=0.)],weight_decay=CONFIG['weight_decay'])