Instructions to use openmodelai/Terra-2.6 with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- PEFT
How to use openmodelai/Terra-2.6 with PEFT:
Task type is invalid.
- Notebooks
- Google Colab
- Kaggle
Download train_cpu.py from openmodelai/Terra-2.6: direct link, hf CLI and curl.
- Browser
- Download file 7.25 kB
-
https://huggingface.co/openmodelai/Terra-2.6/resolve/main/train_cpu.py
- Command line
-
hf download hf://openmodelai/Terra-2.6/train_cpu.py
-
curl -L -o train_cpu.py https://huggingface.co/openmodelai/Terra-2.6/resolve/main/train_cpu.py
7.25 kB
| """Actual low-memory CPU LoRA experiment. Frozen base, adapters in last two blocks.""" | |
| import os | |
| os.environ.setdefault('TOKENIZERS_PARALLELISM', 'false') | |
| os.environ.setdefault('HF_HUB_DISABLE_XET', '1') | |
| import time, json, hashlib, random, resource, sys | |
| from pathlib import Path | |
| import torch | |
| import torch.nn.functional as F | |
| from transformers import AutoModelForCausalLM, AutoTokenizer, set_seed | |
| from peft import LoraConfig, get_peft_model, get_peft_model_state_dict | |
| ROOT=Path(__file__).resolve().parents[1] | |
| if (ROOT/'adapter_model.safetensors').exists() and os.environ.get('TERRA_OVERWRITE_ADAPTER') != '1': | |
| raise SystemExit('Adapter already exists. Work in a COPY and set TERRA_OVERWRITE_ADAPTER=1 to retrain.') | |
| CFG=json.loads((ROOT/'terra_config.json').read_text()) | |
| BASE=CFG['base_model']; REV=CFG['base_revision'] | |
| torch.set_num_threads(2) | |
| torch.set_num_interop_threads(1) | |
| set_seed(42) | |
| START=time.time() | |
| def log(event, **fields): | |
| record={'event':event,'elapsed_s':round(time.time()-START,2),**fields} | |
| print(json.dumps(record,ensure_ascii=False),flush=True) | |
| with (ROOT/'training_events.jsonl').open('a') as f: f.write(json.dumps(record,ensure_ascii=False)+'\n') | |
| def digest_state(model): | |
| h=hashlib.sha256() | |
| for key,v in sorted(get_peft_model_state_dict(model).items()): | |
| h.update(key.encode()); h.update(v.detach().cpu().float().contiguous().numpy().tobytes()) | |
| return h.hexdigest() | |
| log('loading',base_model=BASE,revision=REV,device='cpu',dtype='bfloat16') | |
| tok=AutoTokenizer.from_pretrained(BASE,revision=REV,trust_remote_code=False) | |
| model=AutoModelForCausalLM.from_pretrained(BASE,revision=REV,torch_dtype=torch.bfloat16, | |
| low_cpu_mem_usage=True,trust_remote_code=False,attn_implementation='sdpa') | |
| base_count=sum(p.numel() for p in model.parameters()) | |
| model.config.use_cache=False | |
| last=model.config.num_hidden_layers-1 | |
| modules=[f'model.layers.{i}.self_attn.{name}' for i in [last-1,last] for name in ['q_proj','v_proj','o_proj']] | |
| model=get_peft_model(model,LoraConfig(task_type='CAUSAL_LM',r=8,lora_alpha=16,lora_dropout=0.0, | |
| target_modules=modules,bias='none',base_model_name_or_path=BASE,revision=REV)) | |
| # No input-gradient or checkpointing hook: frozen early blocks need no autograd graph. | |
| trainable=[p for p in model.parameters() if p.requires_grad] | |
| assert trainable and all('lora_' in n for n,p in model.named_parameters() if p.requires_grad) | |
| initial=digest_state(model) | |
| log('loaded',base_parameters=base_count,trainable_parameters=sum(p.numel() for p in trainable), | |
| target_modules=modules,rss_peak_mib=round(resource.getrusage(resource.RUSAGE_SELF).ru_maxrss/1024,1)) | |
| def load_rows(name): | |
| rows=[json.loads(l) for l in (ROOT/'data'/name).read_text().splitlines() if l.strip()] | |
| result=[] | |
| for row in rows: | |
| msgs=[{'role':'system','content':CFG['system_prompt']}]+row['messages'] | |
| pre=tok.apply_chat_template(msgs[:-1],tokenize=True,add_generation_prompt=True) | |
| ids=tok.apply_chat_template(msgs,tokenize=True,add_generation_prompt=False) | |
| assert ids[:len(pre)]==pre and len(ids)<=256 | |
| result.append({'ids':torch.tensor([ids],dtype=torch.long),'prompt_len':len(pre),'language':row['language']}) | |
| return result | |
| train=load_rows('train.example.jsonl'); valid=load_rows('eval.example.jsonl') | |
| # Precompute output-token positions. Compute logits only for supervised answer | |
| # positions, avoiding full-sequence 151k-vocabulary logits and their gradients. | |
| # This is the standard next-token cross-entropy objective, not an approximation. | |
| def loss_for(row): | |
| base=model.get_base_model() | |
| ids=row['ids'] | |
| hidden=base.model(input_ids=ids,attention_mask=torch.ones_like(ids),use_cache=False).last_hidden_state | |
| start=row['prompt_len']-1 | |
| answer_hidden=hidden[0,start:-1,:] | |
| labels=ids[0,start+1:] | |
| logits=base.lm_head(answer_hidden) | |
| loss=F.cross_entropy(logits.float(),labels) | |
| return loss,labels.numel() | |
| def evaluate(): | |
| model.eval(); totals={k:[0.,0] for k in ['ru','en']} | |
| for i,row in enumerate(valid): | |
| value,n=loss_for(row); totals[row['language']][0]+=float(value)*n;totals[row['language']][1]+=n | |
| log('eval_item',item=i+1,loss=round(float(value),6)) | |
| results={k:v[0]/v[1] for k,v in totals.items()} | |
| results['overall']=sum(v[0] for v in totals.values())/sum(v[1] for v in totals.values()) | |
| return results | |
| before=evaluate();log('eval_before',loss=before) | |
| optimizer=torch.optim.AdamW(trainable,lr=5e-4,weight_decay=0.0) | |
| order=list(range(len(train)));random.Random(42).shuffle(order) | |
| losses=[] | |
| for step,index in enumerate(order,1): | |
| model.train();optimizer.zero_grad(set_to_none=True) | |
| t=time.time();loss,n=loss_for(train[index]);assert torch.isfinite(loss) | |
| loss.backward() | |
| norm=torch.nn.utils.clip_grad_norm_(trainable,1.0) | |
| assert torch.isfinite(norm) | |
| optimizer.step();losses.append(float(loss.detach())) | |
| log('train_step',step=step,total_steps=len(order),example=index,language=train[index]['language'], | |
| loss=round(losses[-1],6),grad_norm=round(float(norm),6),answer_tokens=n, | |
| step_s=round(time.time()-t,2),rss_peak_mib=round(resource.getrusage(resource.RUSAGE_SELF).ru_maxrss/1024,1)) | |
| if step%8==0: | |
| model.save_pretrained(ROOT,safe_serialization=True,save_embedding_layers=False) | |
| log('checkpoint',step=step) | |
| after=evaluate();log('eval_after',loss=after) | |
| final=digest_state(model);assert final!=initial | |
| model.save_pretrained(ROOT,safe_serialization=True,save_embedding_layers=False) | |
| old='You are Qwen, created by Alibaba Cloud. You are a helpful assistant.' | |
| assert old in tok.chat_template | |
| tok.chat_template=tok.chat_template.replace(old,CFG['system_prompt'].replace('\\','\\\\').replace("'","\\'").replace('\n','\\n')) | |
| tok.save_pretrained(ROOT) | |
| manifest={'name':'Terra 2.6','publisher':'SpaceModel','base_model':BASE,'base_revision':REV, | |
| 'method':'LoRA SFT on CPU; adapters in final two attention blocks', | |
| 'demo_only':True,'completed':True,'base_parameters':base_count, | |
| 'trainable_parameters':sum(p.numel() for p in trainable),'target_modules':modules, | |
| 'steps':len(order),'train_dialogs':len(train),'eval_dialogs':len(valid), | |
| 'seed':42,'epochs':1,'learning_rate':5e-4,'batch_size':1,'gradient_accumulation':1, | |
| 'rank':8,'alpha':16,'dtype_base':'bfloat16','dtype_adapter':'float32', | |
| 'optimizer':'AdamW','weight_decay':0.0,'gradient_clip_norm':1.0, | |
| 'train_loss_mean':sum(losses)/len(losses),'validation_loss_before':before,'validation_loss_after':after, | |
| 'initial_adapter_sha256':initial,'final_adapter_sha256':final,'adapter_weights_changed':True, | |
| 'data_sha256':{name:hashlib.sha256((ROOT/'data'/name).read_bytes()).hexdigest() for name in ['train.example.jsonl','eval.example.jsonl']}, | |
| 'wall_time_seconds':round(time.time()-START,2),'rss_peak_mib':round(resource.getrusage(resource.RUSAGE_SELF).ru_maxrss/1024,1), | |
| 'torch_version':torch.__version__,'python_version':sys.version, | |
| 'note':'Tiny illustrative run; no independent quality improvement claim. Adapter requires the original base weights.'} | |
| (ROOT/'terra_training.json').write_text(json.dumps(manifest,ensure_ascii=False,indent=2)+'\n') | |
| log('complete',adapter_changed=True,seconds=manifest['wall_time_seconds']) | |