Download brain_lab/backend/ml.py from Chungulus/brain-lab: direct link, hf CLI and curl.
- Browser
- Download file 8.85 kB
-
https://huggingface.co/spaces/Chungulus/brain-lab/resolve/main/brain_lab/backend/ml.py
- Command line
-
hf download hf://spaces/Chungulus/brain-lab/brain_lab/backend/ml.py
-
curl -L -o ml.py https://huggingface.co/spaces/Chungulus/brain-lab/resolve/main/brain_lab/backend/ml.py
8.85 kB
| import base64,io,json,re,threading | |
| from pathlib import Path | |
| import numpy as np | |
| from PIL import Image | |
| import torch | |
| from .networks import SketchEncoder,SpikingReadout,PongSNN,CATEGORIES,NavigationPolicy | |
| ROOT=Path(__file__).resolve().parents[1] | |
| DEVICE='mps' if torch.backends.mps.is_available() else 'cpu' | |
| torch.set_num_threads(12) | |
| LOCK=threading.RLock() | |
| CACHE={} | |
| def decode_image(data): | |
| if not isinstance(data,str) or len(data)>12_000_000: raise ValueError('Image is too large.') | |
| raw=base64.b64decode(data.split(',',1)[-1],validate=True) | |
| im=Image.open(io.BytesIO(raw)) | |
| if im.width*im.height>16_000_000: raise ValueError('Image exceeds 16 megapixels.') | |
| return im.convert('RGB') | |
| def encode_image(im): | |
| b=io.BytesIO(); im.save(b,format='PNG') | |
| return 'data:image/png;base64,'+base64.b64encode(b.getvalue()).decode() | |
| def load(name): | |
| if name in CACHE: return CACHE[name] | |
| path=ROOT/'models'/'hf'/name | |
| if not (path/'config.json').exists() and name!='image': | |
| raise RuntimeError(f'{name} model has not been downloaded. Run scripts/download_models.py.') | |
| from transformers import AutoTokenizer,AutoModelForCausalLM,CLIPProcessor,CLIPModel,BlipProcessor,BlipForConditionalGeneration | |
| if name=='language': | |
| tok=AutoTokenizer.from_pretrained(path,local_files_only=True) | |
| model=AutoModelForCausalLM.from_pretrained(path,local_files_only=True,torch_dtype=torch.float32).to(DEVICE).eval() | |
| obj=(tok,model) | |
| elif name=='vision': | |
| obj=(CLIPProcessor.from_pretrained(path,local_files_only=True),CLIPModel.from_pretrained(path,local_files_only=True).to(DEVICE).eval()) | |
| elif name=='caption': | |
| obj=(BlipProcessor.from_pretrained(path,local_files_only=True),BlipForConditionalGeneration.from_pretrained(path,local_files_only=True).to(DEVICE).eval()) | |
| elif name=='speech': | |
| from transformers import WhisperProcessor,WhisperForConditionalGeneration | |
| obj=(WhisperProcessor.from_pretrained(path,local_files_only=True),WhisperForConditionalGeneration.from_pretrained(path,local_files_only=True).to(DEVICE).eval()) | |
| elif name=='image': | |
| from diffusers import AutoPipelineForText2Image | |
| # Half-precision VAE decoding overflows on some Apple GPUs. Load fp16 files | |
| # into float32 for stable local generation, including the decoder. | |
| obj=AutoPipelineForText2Image.from_pretrained(path,local_files_only=True,torch_dtype=torch.float32,variant='fp16',use_safetensors=True).to(DEVICE) | |
| obj.enable_attention_slicing() | |
| else: raise ValueError('Unknown model') | |
| CACHE[name]=obj | |
| return obj | |
| def language(prompt,max_tokens=160): | |
| with LOCK,torch.inference_mode(): | |
| tok,model=load('language') | |
| text=tok.apply_chat_template([{'role':'system','content':'You are a helpful assistant for a classroom AI brain exhibit. Follow the requested format exactly. Be brief.'},{'role':'user','content':prompt}],tokenize=False,add_generation_prompt=True) | |
| inputs=tok(text,return_tensors='pt').to(DEVICE) | |
| out=model.generate(**inputs,max_new_tokens=max_tokens,do_sample=False,pad_token_id=tok.eos_token_id) | |
| return tok.decode(out[0,inputs.input_ids.shape[1]:],skip_special_tokens=True).strip() | |
| def image_features(im): | |
| with LOCK,torch.inference_mode(): | |
| proc,model=load('vision'); data=proc(images=im,return_tensors='pt').to(DEVICE) | |
| feat=model.get_image_features(**data); feat=feat/feat.norm(dim=-1,keepdim=True) | |
| return feat[0].cpu().numpy() | |
| def text_features(texts): | |
| with LOCK,torch.inference_mode(): | |
| proc,model=load('vision'); data=proc(text=texts,return_tensors='pt',padding=True,truncation=True).to(DEVICE) | |
| feat=model.get_text_features(**data); feat=feat/feat.norm(dim=-1,keepdim=True) | |
| return feat.cpu().numpy() | |
| def classify_image(im,labels): | |
| feat=image_features(im); refs=text_features(['a photo of '+label for label in labels]) | |
| scores=torch.tensor(refs@feat*30).softmax(0).numpy() | |
| return sorted([{'label':l,'probability':float(p)} for l,p in zip(labels,scores)],key=lambda a:-a['probability']),feat | |
| def caption(im): | |
| with LOCK,torch.inference_mode(): | |
| proc,model=load('caption'); data=proc(images=im,return_tensors='pt').to(DEVICE) | |
| out=model.generate(**data,max_new_tokens=45) | |
| return proc.decode(out[0],skip_special_tokens=True) | |
| def generate_image(prompt,seed=21): | |
| with LOCK,torch.inference_mode(): | |
| pipe=load('image'); gen=torch.Generator(device='cpu').manual_seed(seed) | |
| im=pipe(prompt=prompt,num_inference_steps=2,guidance_scale=0.,height=512,width=512,generator=gen).images[0] | |
| return encode_image(im) | |
| def sketch_input(im): | |
| gray=255-np.asarray(im.convert('L')) | |
| ys,xs=np.where(gray>30) | |
| if len(xs)<8: return None | |
| bbox=(int(xs.min()),int(ys.min()),int(xs.max())+1,int(ys.max())+1) | |
| crop=Image.fromarray(gray).crop(bbox) | |
| crop.thumbnail((25,25),Image.Resampling.LANCZOS) | |
| out=Image.new('L',(28,28)); out.paste(crop,((28-crop.width)//2,(28-crop.height)//2)) | |
| return torch.tensor(np.asarray(out,dtype=np.float32)[None,None]/255) | |
| def sketch(im,threshold=1.): | |
| x=sketch_input(im) | |
| if x is None: return {'candidates':[],'spikes':[],'membrane':[],'message':'Add a few strokes to begin.'} | |
| with LOCK,torch.inference_mode(): | |
| if 'sketch' not in CACHE: | |
| ck=torch.load(ROOT/'models'/'sketch.pt',map_location='cpu',weights_only=True) | |
| enc=SketchEncoder(); enc.load_state_dict(ck['encoder']); enc.eval() | |
| net=SpikingReadout(); net.load_state_dict(ck['snn']); net.eval() | |
| CACHE['sketch']=(enc,net,ck['mean'],ck['std']) | |
| enc,net,mean,std=CACHE['sketch'] | |
| features=(enc.features(x)-mean)/std | |
| logits,spikes,mem=net(features,threshold,True) | |
| scores=logits[0].softmax(0).numpy() | |
| ids=np.argsort(-scores)[:5] | |
| return {'candidates':[{'label':CATEGORIES[i],'probability':float(scores[i])} for i in ids], | |
| 'spikes':spikes[:,0,:32].int().tolist(),'membrane':mem[:,0,0].tolist(), | |
| 'spike_count':int(spikes.sum()),'threshold':threshold,'model':'CNN encoder → trained LIF layer → linear class readout'} | |
| def pong_config(): | |
| receipt=json.loads((ROOT/'models'/'pong_v2_receipt.json').read_text()) | |
| return {'model':'Target-position LIF SNN','optimal_threshold':receipt['optimal_threshold'], | |
| 'steps':10,'neurons':64,'validation_target_mae':receipt['threshold_validation_mae'][str(receipt['optimal_threshold'])], | |
| 'imitation_accuracy':receipt['imitation_accuracy'],'test_states':receipt['test_states'], | |
| 'benchmark_scope':receipt['benchmark_scope']} | |
| def pong(state,threshold): | |
| if len(state)!=5 or not np.isfinite(state).all():raise ValueError('Use five finite game observations.') | |
| with LOCK,torch.inference_mode(): | |
| if 'pong' not in CACHE: | |
| net=PongSNN();net.load_state_dict(torch.load(ROOT/'models'/'pong_v2.pt',weights_only=True));net.eval();CACHE['pong']=net | |
| goal,spikes,mem=CACHE['pong'](torch.tensor([state],dtype=torch.float32),threshold,True) | |
| active=bool(spikes.sum()>0) | |
| target=float(goal[0].clamp(.095,.905)) if active else float(state[4]) | |
| delta=target-state[4] | |
| action=-1 if delta<-.025 else 1 if delta>.025 else 0 | |
| return {'action':action,'target_y':target,'decision_active':active,'model':'Target-position LIF SNN', | |
| 'spikes':spikes[:,0,:32].int().tolist(),'membrane':mem[:,0,0].tolist(), | |
| 'spike_count':int(spikes.sum()),'threshold':threshold,'neurons':64,'steps':10} | |
| def navigate(dx,dy,blocked): | |
| with LOCK,torch.inference_mode(): | |
| if 'navigation' not in CACHE: | |
| net=NavigationPolicy(); net.load_state_dict(torch.load(ROOT/'models'/'navigation.pt',weights_only=True)); net.eval(); CACHE['navigation']=net | |
| logits=CACHE['navigation'](torch.tensor([[dx,dy,*blocked]],dtype=torch.float32))[0] | |
| # Physical collision gate rejects impossible movement, independently of policy. | |
| for i,b in enumerate(blocked): | |
| if b: logits[i]=-1e6 | |
| return int(logits.argmax()),logits.softmax(0).tolist() | |
| def transcribe(path): | |
| import subprocess,tempfile,soundfile as sf | |
| with tempfile.NamedTemporaryFile(suffix='.wav') as out: | |
| subprocess.run(['ffmpeg','-v','error','-y','-i',str(path),'-ar','16000','-ac','1',out.name],check=True,capture_output=True) | |
| audio,sr=sf.read(out.name) | |
| if len(audio)>16000*30: raise ValueError('Please record 30 seconds or less.') | |
| with LOCK,torch.inference_mode(): | |
| proc,model=load('speech'); inp=proc(audio,sampling_rate=16000,return_tensors='pt').input_features.to(DEVICE) | |
| out=model.generate(inp,max_new_tokens=128) | |
| return proc.batch_decode(out,skip_special_tokens=True)[0].strip() | |