"""V5 random-initialized LM/chat pretraining: the V4.3 recipe and data at d512/L8. Identical to `joint_v43` except the fixed model size and architecture identity, so a run on the first ten V4.3 epoch maps is a size-only comparison with V4.3 controlled-10ep. """ 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_v5 import TinyGPTV5,GPTV5Config,PARAMETERS from vimeml.training.model_factory_v5 import ARCHITECTURE,FORMAT 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='v5' TASK_CONTRACT={ 'objective':'single_task_lm_token_normalized', 'tasks':['old_web_lm','expansion_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,'expansion':'nine additional FineWeb2-Edu shards, cross-pool deduplicated', 'data':'frozen V4.3 mixture (corpus-v43-mixed-c128); leading epoch maps only', 'model':'d512/L8/8 heads/SwiGLU 1280, V4 8K tokenizer', } def read(path): return json.loads(Path(path).read_text('utf-8')) def chat_store_of(request): for bank in request['joined_stores']: if bank['token_dir'].rstrip('/').endswith('corpus-v41-chat-8k'): return bank raise ValueError('V5 mixture must contain the frozen chat bank') def expansion_store_of(request): for bank in request['joined_stores']: if 'corpus-v43-exp8k' in bank['token_dir']: return bank raise ValueError('V5 mixture must contain the expansion bank') def build_plan(config,root=ROOT,verify_files=True): root=Path(root) if config.get('stage')!=STAGE or config.get('architecture')!=ARCHITECTURE or config.get('initialization')!={'mode':'random'}: raise ValueError('V5 requires explicit random initialization; no source checkpoint') GPTV5Config(**config['model']) s=dict(config['training']) 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('V5 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('V5 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 len(mix['epochs'])!=request['epochs'] or request['seed']!=s['seed'] or request['crop_probability']!=s['prefix_crop_probability'] or request['crop_minimum']!=8 or request['chat_fraction']!=.05 or not 1<=s['epochs']<=request['epochs']): raise ValueError('Frozen V4.3 mixture differs from V5 training configuration') if file_sha(mix_dir/'manifest.json')!=config['dataset_sha256']: raise ValueError('V4.3 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/expansion/chat bank manifest') if manifest['tokenizer_model_sha256']!=TOKENIZER_SHA or manifest['vocab_size']!=8192: raise ValueError('LM/expansion/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']) expansion=expansion_store_of(request) signatures['expansion_tokens']=expansion['token_manifest_sha256'] for bank in request['joined_stores'][:-1]: required[root/bank['keep']]=bank['keep_sha256'] for record in mix['epochs'][:s['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_v5.py']=file_sha(root/'scripts/training/train_v5.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['plan_epochs']=request['epochs'] s['updates_per_epoch']=[math.ceil(r['windows']/s['lm_batch']) for r in mix['epochs'][:s['epochs']]] s['windows_per_epoch']=[r['windows'] for r in mix['epochs'][:s['epochs']]] s['max_steps']=sum(s['updates_per_epoch']) return {'stage':STAGE,'parameters':PARAMETERS,'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): 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, expansion 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'] exp_stats=read(root/exp_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']) ev=validate(model,exp_validation,device,exp_stats['prediction_pairs'],exp_stats['characters']) restore_rng(before) report={'epoch':e+1,'step':state['step'],'lm':lv,'chat':chat,'expansion':ev} 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,exp_val): data.close()