Text Classification
PEFT
lora
document-question-answering
structured-decisions
calibration
synthetic-evaluation
Instructions to use botp/Solomon with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- PEFT
How to use botp/Solomon with PEFT:
Task type is invalid.
- Notebooks
- Google Colab
- Kaggle
File size: 13,978 Bytes
1d2de8a | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 | """Final normalized feature extraction, preserving the qualified CUDA engine."""
import hashlib,json
from pathlib import Path
import numpy as np
def sha(path):return hashlib.sha256(Path(path).read_bytes()).hexdigest()
def token_ids_for(engine,state,block,n):
text=engine._render(state['parts'],block);raw=engine.t.encode(text,add_special_tokens=False);ids=[]
for c in 'ABCDEFGHIJ'[:n]:
ext=engine.t.encode(text+c,add_special_tokens=False)
if ext[:-1]!=raw or len(ext)!=len(raw)+1:raise ValueError('unstable answer token continuation')
ids.append(ext[-1])
return ids
def capture(engine,state,block,n):
"""Prehook sees precisely the post-final-norm input used by the vocabulary head."""
captured=[]
def hook(module,args):
h=args[0]
if h.ndim!=2 or h.shape[0]!=1:raise ValueError('unexpected final head input shape')
captured.append(h.detach().float().cpu().numpy()[0].copy())
handle=engine.model.lm_head.register_forward_pre_hook(hook)
try:result=engine.ask(state,block,n,execution='cached')
finally:handle.remove()
if len(captured)!=1:raise ValueError('expected exactly one vocabulary projection')
ids=token_ids_for(engine,state,block,n)
if not np.isfinite(captured[0]).all():raise ValueError('nonfinite hidden features')
return captured[0],ids,result
def copy_letter_head(engine):
ids=[]
for c in 'ABCDEFGHIJ':
pieces=engine.t.encode(c,add_special_tokens=False)
if len(pieces)!=1:raise ValueError('letters must be one token')
ids.append(pieces[0])
head=engine.model.lm_head;w=head.weight.detach()[ids].float().cpu().numpy().copy();b=getattr(head,'bias',None)
return {'weight':w,'bias':np.zeros(10,np.float32) if b is None else b.detach()[ids].float().cpu().numpy().copy(),'token_ids':np.array(ids,np.int64),'vocabulary_size':np.array(head.weight.shape[0],np.int64),'bias_present':np.array(b is not None)}
def atomic_json(path,value):
path=Path(path);tmp=path.with_suffix(path.suffix+'.tmp');tmp.write_text(json.dumps(value,sort_keys=True,indent=2));tmp.replace(path)
def write_document(path,features,token_ids,results,metadata,identity):
"""NPZ first, then checksummed completion JSON. No cumulative shard rewrite."""
path=Path(path);n=len(features)
if not n or not(len(token_ids)==len(results)==len(metadata)==n):raise ValueError('document arrays must align')
h=np.asarray(features,np.float32);logits=np.zeros((n,10),np.float32);tokens=np.full((n,10),-1,np.int64);widths=[]
if h.ndim!=2 or not np.isfinite(h).all():raise ValueError('invalid document feature matrix')
rows=[]
for i,(ids,r,m) in enumerate(zip(token_ids,results,metadata)):
values=np.asarray(r['letter_logits'],np.float32);width=len(ids)
if not 2<=width<=10 or values.shape!=(width,) or not np.isfinite(values).all():raise ValueError('invalid teacher row')
logits[i,:width]=values;tokens[i,:width]=ids;widths.append(width)
rows.append({**m,'n':width,'mass':float(r['mass']),'top_is_letter':bool(r['top_is_letter']),'prompt_tokens':int(r['prompt_tokens']),'branch_tokens':int(r['branch_tokens']),'reused_prefix_tokens':int(r['reused_prefix_tokens'])})
tmp=path.with_suffix('.npz.tmp')
with tmp.open('wb') as f:np.savez(f,hidden=h,teacher_logits=logits,token_ids=tokens,widths=np.asarray(widths,np.int64))
tmp.replace(path)
report={'complete':True,'identity':identity,'rows':rows,'npz_sha256':sha(path),'feature_shape':list(h.shape),'feature_kind':'final-normalized-lm-head-input','storage':'uncompressed-npz-float32'}
atomic_json(path.with_suffix('.json'),report)
return report
def validate_document(path,identity,expected_ids):
path=Path(path);meta=path.with_suffix('.json')
if not path.exists() or not meta.exists():return False
r=json.loads(meta.read_text())
if r.get('complete')is not True or r.get('identity')!=identity or r.get('npz_sha256')!=sha(path) or [x['id'] for x in r['rows']]!=list(expected_ids):raise ValueError('resumed document identity mismatch')
return True
class TrainedHeads:
"""Validated saved answer/correctness heads. No backbone or label access."""
def __init__(self,directory,*,expected_report_sha256=None,expected_weights_sha256=None):
directory=Path(directory);report_path=directory/'report.json';weights_path=directory/'weights.npz'
self.report_sha256=sha(report_path);self.weights_sha256=sha(weights_path)
if expected_report_sha256 is not None and self.report_sha256!=expected_report_sha256:raise ValueError('selected head report changed')
if expected_weights_sha256 is not None and self.weights_sha256!=expected_weights_sha256:raise ValueError('selected head weights changed')
self.report=json.loads(report_path.read_text())
if self.report['weights_sha256']!=self.weights_sha256:raise ValueError('trained head weight checksum mismatch')
self.models={}
with np.load(weights_path,allow_pickle=False) as archive:
for kind,entries in self.report['models'].items():
self.models[kind]={}
for key,fields in entries.items():
model={name:archive[value['array']].astype(np.float64) if isinstance(value,dict) and set(value)=={'array'} else value for name,value in fields.items()}
if any(not np.isfinite(value).all() for value in model.values() if isinstance(value,np.ndarray)):raise ValueError('nonfinite trained head')
if kind=='heads':
if model['weight'].ndim!=2 or model['weight'].shape[0]!=10 or model['bias'].shape!=(10,):raise ValueError('invalid answer head shape')
elif kind=='correctness':
if model['mean'].ndim!=1 or model['scale'].shape!=model['mean'].shape or np.any(model['scale']<=0) or model['weights'].shape!=(len(model['mean'])+1,):raise ValueError('invalid correctness head shape')
else:raise ValueError('unknown trained model kind')
self.models[kind][key]=model
if not self.models.get('heads') or not self.models.get('correctness'):raise ValueError('both trained answer and correctness heads required')
def answer(self,hidden,key,n):
if key not in self.models['heads']:raise ValueError('no trained answer head for '+str(key))
if type(n)is not int or not 2<=n<=10:raise ValueError('invalid head output width')
h=np.asarray(hidden,np.float64);model=self.models['heads'][key]
if h.shape!=(model['weight'].shape[1],) or not np.isfinite(h).all():raise ValueError('invalid hidden state for answer head')
return (model['weight']@h+model['bias'])[:n]
def correctness(self,task,branches):
from scipy.special import expit
if task not in self.models['correctness'] or not branches:raise ValueError('no trained whole-answer correctness head')
hidden=np.asarray([r['hidden'] for r in branches],np.float64);probs=[]
if hidden.ndim!=2 or not np.isfinite(hidden).all():raise ValueError('invalid correctness hidden representations')
for row in branches:
p=np.asarray(row['probabilities'],float)
if p.ndim!=1 or len(p)<2 or not np.isfinite(p).all() or np.any(p<0) or abs(p.sum()-1)>1e-6:raise ValueError('invalid correctness answer distribution')
probs.append(p)
feature=np.r_[hidden.mean(0),min(float(p.max()) for p in probs),min(float(np.sort(p)[-1]-np.sort(p)[-2]) for p in probs),max(float(-np.sum(p*np.log(np.maximum(p,1e-300)))/np.log(len(p))) for p in probs),float(len(branches)),float(sum(len(p) for p in probs))]
model=self.models['correctness'][task]
if feature.shape!=model['mean'].shape:raise ValueError('whole-answer confidence feature width mismatch')
value=float(expit(np.r_[1.,(feature-model['mean'])/model['scale']]@model['weights']))
return {'reliability':value,'features':feature.tolist(),'task':task,'target':'whole_return_exact_correctness','calibration_established':False,'heads_sha256':self.weights_sha256}
def semantic_head_key(task,block):
from solomon.engine_contract import SUFFICIENCY,SINGLE_R,SINGLE_S,ORDERED_R,ORDERED_S,LABEL,BOOLEAN_TASK
if task not in ('boolean','single','ordered','multilabel','entity'):raise ValueError('explicit answering task required')
if block.startswith(SUFFICIENCY) and task in ('single','ordered'):return task+'/sufficiency3'
for prefix,t,form in ((SINGLE_R,'single','R'),(SINGLE_S,'single','S'),(ORDERED_R,'ordered','R'),(ORDERED_S,'ordered','S')):
if block.startswith(prefix) and task==t:return task+'/choice'+form
if block.startswith(LABEL) and task=='multilabel':return 'multilabel/state4'
if block.startswith(BOOLEAN_TASK):
if task in ('boolean','entity'):return task+'/state4'
if task=='ordered':return 'ordered/threshold4'
raise ValueError('task block has no trained semantic head mapping')
from solomon.engine_reasoning import ReasoningEngine
class HeadsEngine(ReasoningEngine):
"""Question-only backbone with trained small heads; native LM head retained for reasoning.
Wrap a complete readout in task_context(task), or supply ask(head_key=...).
Answer projection is deliberately evaluated in CPU float64 on the exact copied
float32 hidden vector to match saved-feature evaluation; no speedup is claimed.
"""
def __init__(self,heads_dir,*,expected_base_fingerprint,expected_report_sha256=None,expected_weights_sha256=None,**kwargs):
import threading
self.heads=TrainedHeads(heads_dir,expected_report_sha256=expected_report_sha256,expected_weights_sha256=expected_weights_sha256)
super().__init__(**kwargs)
if self.identity['fingerprint']!=expected_base_fingerprint:raise ValueError('trained head backbone/adapter runtime mismatch')
self.base_identity=dict(self.identity);self._head_task=None;self._head_lock=threading.RLock()
self.identity={**self.identity,'base_fingerprint':expected_base_fingerprint,'trained_heads_sha256':self.heads.weights_sha256,'trained_heads_report_sha256':self.heads.report_sha256,'heads_runtime_sha256':sha(__file__),'answer_projection':'trained-semantic-head-float64-cpu'}
self.identity.pop('fingerprint',None);self.identity['fingerprint']=hashlib.sha256(json.dumps(self.identity,sort_keys=True).encode()).hexdigest()
def task_context(self,task):
from contextlib import contextmanager
@contextmanager
def context():
with self._head_lock:
previous=self._head_task;self._head_task=task
try:yield self
finally:self._head_task=previous
return context()
def prefill(self,document):
with self._head_lock:return super().prefill(document)
def ask(self,state,block,n_letters,execution='cached',head_key=None):
import copy
with self._head_lock:
key=head_key or semantic_head_key(self._head_task,block)
if key not in self.heads.models['heads']:raise ValueError('missing trained head '+key)
if self.placement!='question':raise ValueError('trained heads require question-only adaptation')
if execution not in ('cached','full'):raise ValueError('invalid execution')
torch=self.torch;p=state['prefix_tokens'];previous=self.ctx['start']
try:
with torch.inference_mode():
text=self._render(state['parts'],block);ids,embeds,pos,_=self._encode(state['parts'],text,state['features'])
if not torch.equal(ids[:,:p],state['prefix_ids']):raise ValueError('head question prefix mismatch')
self.ctx['start']=(0 if execution=='cached' or self.placement=='full' else p) if self.adapter else None
if execution=='cached':h=self.lm(inputs_embeds=embeds[:,p:],position_ids=pos[...,p:],past_key_values=copy.deepcopy(state['cache']),use_cache=True).last_hidden_state
else:h=self.lm(inputs_embeds=embeds,position_ids=pos,use_cache=False).last_hidden_state
hidden=h[0,-1].float().cpu().numpy().copy()
logits=self.heads.answer(hidden,key,n_letters);probs=np.exp(logits-logits.max());probs/=probs.sum()
return {'letter_logits':logits,'probabilities':probs,'prediction':int(probs.argmax()),'hidden':hidden.tolist(),'head_key':key,'mass':None,'top_is_letter':None,'mass_available':False,'execution':execution,'fallback':'','prompt_tokens':int(ids.shape[1]),'branch_tokens':int(ids.shape[1])-p,'input_tokens':int(ids.shape[1])-p,'reused_prefix_tokens':p if execution=='cached' else 0,'generation_tokens':0,'fingerprint':self.identity['fingerprint']}
finally:self.ctx['start']=previous
def estimate(self,state,block,n,stage='fast',max_new_tokens=512):
if stage!='fast' or not any('image' in part for part in state['parts']):
return super().estimate(state,block,n,stage=stage,max_new_tokens=max_new_tokens)
if type(n)is not int or not 2<=n<=10:raise ValueError('invalid letter count')
# Reuse prefetched vision features; no transformer branch is evaluated.
with self._head_lock,self.torch.inference_mode():
ids,_,_,_=self._encode(state['parts'],self._render(state['parts'],block),state['features'])
prefix=state['prefix_tokens']
if not self.torch.equal(ids[:,:prefix],state['prefix_ids']):raise ValueError('image budget prefix mismatch')
return {'input_tokens':int(ids.shape[1])-prefix,'generated_tokens':0,'branches':1,
'full_prompt_tokens':int(ids.shape[1]),'reused_prefix_tokens':prefix}
def predict_correctness(self,task,branches):return self.heads.correctness(task,branches)
def reason(self,*args,**kwargs):
with self._head_lock:return super().reason(*args,**kwargs)
|