"""V4.2 random-initialized LM/chat pretraining; V4.1 minus the conversion pool.""" import copy import hashlib import json import math from pathlib import Path import random import signal import time import torch from vimeml.training.data import SentenceWindowDataset,file_sha,write_json from vimeml.training.data_mixture import mixture_dataset from vimeml.training.model_v4 import TinyGPTV4,GPTV4Config from vimeml.training.train import (optimizer_for,learning_rate,atomic_checkpoint,rng_state, restore_rng,resolve_precision,amp_context) from vimeml.posttraining.streams import CommittedStream,loader_for,microbatches from vimeml.posttraining.train import validate from vimeml.posttraining.data import TOKENIZER_SHA ROOT=Path(__file__).resolve().parents[3] STAGE='v4.2' TASK_CONTRACT={ 'objective':'single_task_lm_token_normalized', 'tasks':['old_web_lm','chat_lm'], 'normalization':'total supervised tokens per update', 'lm':'ordinary_unmarked_LM_all_valid_targets', 'conversion':'none; V4.1 conversion pool removed', 'ime_validation':'none_in_training; deferred post-training over saved epoch checkpoints', 'context_length':128,'tokenizer_sha256':TOKENIZER_SHA, 'chat_fraction':0.05, } def read(path): return json.loads(Path(path).read_text('utf-8')) def build_plan(config,root=ROOT,verify_files=True): root=Path(root) if config.get('stage')!=STAGE or config.get('architecture')!='tiny_gpt_v4' or config.get('initialization')!={'mode':'random'}: raise ValueError('V4.2 requires explicit random initialization; no source checkpoint') GPTV4Config(**config['model']) s=dict(config['training']) if s['epochs']!=20: raise ValueError('Use the frozen 20-epoch V4.2 budget') for key in ('lm_batch','lm_microbatch','epochs','num_workers','cpu_threads','log_every', 'eval_every','checkpoint_every','validation_batch','bucket_multiplier','warmup_steps'): if type(s[key]) is not int or s[key]<1: raise ValueError(f'Invalid training integer: {key}') if type(s.get('smoke_updates')) is not int or s['smoke_updates']<0: raise ValueError('Invalid smoke update budget') if type(s['seed']) is not int or not 0<=s['seed']<2**63: raise ValueError('Invalid random seed') for key in ('learning_rate','min_learning_rate','beta1','beta2','weight_decay','grad_clip'): if not math.isfinite(s[key]) or s[key]<=0: raise ValueError(f'Invalid training scalar: {key}') compiled=config['runtime']['compile_backbone'] if (s['device']=='cuda' and s['precision']=='bf16' and compiled): pass elif (s['device']=='cpu' and s['precision']=='fp32' and not compiled and s['smoke_updates']>0): pass else: raise ValueError('V4.2 formal training is CUDA/BF16/compiled; only smoke runs are CPU/FP32') if (not 0=1 or s['beta2']>=1 or not 0<=s['prefix_crop_probability']<1 or s['lm_microbatch']>s['lm_batch']): raise ValueError('V4.2 optimizer/microbatch contract mismatch') mix_dir=root/config['data_mixture']['directory'] mix=read(mix_dir/'manifest.json') request=mix['request'] if (mix['status']!='complete' or request['epochs']!=20 or len(mix['epochs'])!=20 or request['seed']!=s['seed'] or request['crop_probability']!=s['prefix_crop_probability'] or request['crop_minimum']!=8 or request['chat_fraction']!=.05): raise ValueError('Frozen V4.2 mixture differs from training configuration') if file_sha(mix_dir/'manifest.json')!=config['dataset_sha256']: raise ValueError('V4.2 dataset manifest changed') required={} signatures={'dataset':config['dataset_sha256']} for bank in request['joined_stores']: folder,index=root/bank['token_dir'],root/bank['index_dir'] manifest=read(folder/'manifest.json') if file_sha(folder/'manifest.json')!=bank['token_manifest_sha256'] or file_sha(index/'manifest.json')!=bank['window_manifest_sha256']: raise ValueError('Changed LM/chat bank manifest') if manifest['tokenizer_model_sha256']!=TOKENIZER_SHA or manifest['vocab_size']!=8192: raise ValueError('LM/chat vocabulary mismatch') windows=read(index/'manifest.json') for split in ('train','validation'): for suffix in ('tokens.bin','offsets.bin','sources.bin'): required[folder/f'{split}.{suffix}']=manifest['output_sha256'][f'{split}.{suffix}'] required[index/f'{split}.windows.npz']=windows['splits'][split]['index_sha256'] required[folder/'manifest.json']=bank['token_manifest_sha256'] required[index/'manifest.json']=bank['window_manifest_sha256'] primary=request['joined_stores'][0] if config['token_dir']!=primary['token_dir'] or config['index_dir']!=primary['index_dir']: raise ValueError('Primary model tokenizer/LM bank mismatch') signatures.update(tokens=primary['token_manifest_sha256'],windows=primary['window_manifest_sha256']) required[root/request['old_keep']]=request['old_keep_sha256'] for record in mix['epochs']: required[mix_dir/record['file']]=record['sha256'] required[mix_dir/'manifest.json']=config['dataset_sha256'] required[mix_dir/'request.json']=file_sha(mix_dir/'request.json') tokenizer=root/config['tokenizer_dir']/'tokenizer.model' if file_sha(tokenizer)!=TOKENIZER_SHA: raise ValueError('Tokenizer changed') required[tokenizer]=TOKENIZER_SHA code={p.relative_to(root).as_posix():file_sha(p) for p in sorted((root/'src/vimeml').rglob('*.py'))} code['scripts/training/train_v42.py']=file_sha(root/'scripts/training/train_v42.py') signatures['code']=hashlib.sha256(json.dumps(code,sort_keys=True).encode()).hexdigest() if verify_files: for path,digest in required.items(): if file_sha(path)!=digest: raise ValueError('Changed training dependency: '+str(path)) s['updates_per_epoch']=[math.ceil(r['windows']/s['lm_batch']) for r in mix['epochs']] s['windows_per_epoch']=[r['windows'] for r in mix['epochs']] s['max_steps']=sum(s['updates_per_epoch']) return {'stage':STAGE,'parameters':15_070_848,'settings':s,'signatures':signatures,'code_sha256':code, 'required_files':{p.relative_to(root).as_posix():h for p,h in required.items()}, 'request':request,'task_contract':TASK_CONTRACT} def initial_state(): return {'epoch':0,'epoch_batch':0,'step':0,'lm_cursor':{'epoch':0,'batch':0,'offset':0}, 'epoch_results':[],'best_lm':None,'training_seconds':0., 'totals':{'lm':{k:0 for k in ('examples','input_tokens','supervised_tokens','padded_positions')}}} def consumed_examples(state,plan): """Examples consumed by committed updates; the tail batch is smaller.""" s=plan['settings'] e,b=state['epoch'],state['epoch_batch'] updates=s['updates_per_epoch'] total=sum(s['windows_per_epoch'][:e]) if eupdates[e]): raise ValueError('Invalid committed LM epoch cursor') if state['step']!=sum(updates[:e])+b: raise ValueError('Step does not match committed epoch updates') if state['totals']['lm']['examples']!=consumed_examples(state,plan): raise ValueError('LM exposure does not match committed updates') if state['lm_cursor']!={'epoch':e,'batch':b,'offset':0}: raise ValueError('LM stream cursor mismatch') epochs=[r['epoch'] for r in state['epoch_results']] allowed=list(range(1,e+1)) if e=s['smoke_updates']: break if s['smoke_updates'] and state['step']>=s['smoke_updates']: break if requested: break if not any(r['epoch']==e+1 for r in state['epoch_results']): save() write_json(output/'progress.json',progress(state,plan,'validating')) print(f'[epoch {e+1}] full old LM and chat validation',flush=True) before=rng_state() with fp32_evaluation(model): old_stats=read(root/config['token_dir']/'manifest.json')['splits']['validation'] chat_stats=read(root/chat_bank['token_dir']/'manifest.json')['splits']['validation'] lv=validate(model,old_validation,device,old_stats['prediction_pairs'],old_stats['characters']) chat=validate(model,chat_validation,device,chat_stats['prediction_pairs'],chat_stats['characters']) restore_rng(before) report={'epoch':e+1,'step':state['step'],'lm':lv,'chat':chat} roles=[] if state['best_lm'] is None or lv['bpc']=s['smoke_updates']: save() terminal='smoke_complete' else: terminal='complete' if state['epoch']==s['epochs'] else 'interrupted' save() result=progress(state,plan,terminal) result['checkpoints']={n:str(output/f'{n}.pt') for n in ('best-lm','last') if (output/f'{n}.pt').exists()} result['epoch_checkpoints']=sorted(p.name for p in output.glob('epoch-*.pt')) if device.type=='cuda': result['peak_allocated_MiB']=torch.cuda.max_memory_allocated(device)/2**20 result['peak_reserved_MiB']=torch.cuda.max_memory_reserved(device)/2**20 write_json(output/'summary.json',result) write_json(output/'progress.json',result) return result except BaseException as error: write_json(output/'failure.json',{'status':'failed','active':True,'error_type':type(error).__name__, 'error':str(error),'last_committed_in_memory_step':state['step'],'safe_resume_checkpoint':str(output/'last.pt')}) raise finally: writer.close();metrics.close() for sig,handler in handlers.items(): signal.signal(sig,handler) lm_train.close() for data in (lm_val,chat_val): data.close()