"""AMOS Qwen3.5 Core AI adapter. No PyTorch neural model is loaded.""" import json,sys from pathlib import Path import numpy as np import torch from coreai.runtime import AIModel,NDArray,SpecializationOptions from transformers import AutoTokenizer,Qwen2VLImageProcessorPil class AmosDecoder: @classmethod async def load(cls,bundle): self=cls();self.root=Path(bundle).resolve();sys.path.insert(0,str(self.root/'serve')) import amos.decoder as decoder decoder.IMAGE_PIXELS=224*224 self.manifest=json.loads((self.root/'manifest.json').read_text()) self.codec=decoder.Codec(AutoTokenizer.from_pretrained(self.root/'model'),Qwen2VLImageProcessorPil.from_pretrained(self.root/'model',size={'shortest_edge':224*224,'longest_edge':224*224}),255) self.codec.echo=self.manifest['echo'] self.temperature=decoder.calibration(self.root) or {};self.span=decoder.span_decoding(self.root) self.models=[];self.functions={} names=['vision','heads'] if self.manifest.get('text_layout')=='layer-sharded':names+=['embedding','finish']+[r['asset'].removesuffix('.aimodel') for r in self.manifest['layers']] else:names+=['text'] for name in names: m=await AIModel.load(self.root/(name+'.aimodel'),SpecializationOptions.default());self.models.append(m);self.functions[name]=m.load_function('main') return self async def raw(self,state,questions,images=()): from amos.decoder import expand from PIL import Image def resize(im):return im.convert('RGB').resize((224,224),Image.Resampling.BICUBIC) images=[resize(im) for im in images] questions=[{**q,'options':[resize(o) if isinstance(o,Image.Image) else o for o in q['options']]} if 'options' in q else q for q in questions] flat,owner=expand(questions) ids,at,extra=self.codec.encode_case(state,flat,images) n=len(ids);L=self.manifest['sequence_length'];d=self.manifest['hidden_size'] if n>L:raise ValueError(f'Request needs {n} tokens; Core AI capacity is {L}.') args={'input_ids':np.full((1,L),self.codec.pad,np.int32),'attention_mask':np.zeros((1,L),np.int32),'position_ids':np.zeros((4,1,L),np.int32),'visual_embeddings':np.zeros((1,L,d),np.float32),'visual_mask':np.zeros((1,L,1),bool)} args['input_ids'][0,:n]=ids;args['attention_mask'][0,:n]=1;args['position_ids'][0,0,:n]=np.arange(n) image_at={s:(rows,cols) for s,rows,cols in extra.get('images',[])} features=[] if image_at: pv=extra['pixel_values'].numpy() for start in range(0,len(pv),196): out=await self.functions['vision']({'pixel_values':NDArray(pv[start:start+196].astype(np.float32))});features.append(out[self.manifest['vision_output']].numpy()) cursor=0;offset=0;fi=0 while cursor=np.arange(L)[None,:])[None,None]&args['attention_mask'][:,None,None,:].astype(bool) for layer in self.manifest['layers']: result=await self.functions[layer['asset'].removesuffix('.aimodel')]({'hidden_states':h,'cos':cos,'sin':sin,'position_ids':NDArray(args['position_ids'][0].copy()),'attention_mask':NDArray(causal)}) h=result[layer['output']] output=await self.functions['finish']({'hidden_states':h}) else:output=await self.functions['text']({k:NDArray(v) for k,v in args.items()}) hidden,logits=[output[k].numpy() for k in self.manifest['outputs']] head_results={} for i,(q,pos) in enumerate(zip(flat,at)): if q['type'] not in ['span','region']:continue first,offsets=extra['state'];state_hidden=np.zeros((L,d),np.float32);state_hidden[:len(offsets)]=hidden[0,first:first+len(offsets)] cells=np.zeros((49,d),np.float32) if extra.get('images'): start,rows,cols=extra['images'][0];cells=hidden[0,start:start+49].copy() if q['type']=='region' and not extra.get('images'):raise ValueError('Region requires a state image') ho=await self.functions['heads']({'question_hidden':NDArray(hidden[0,pos].copy()),'state_hidden':NDArray(state_hidden),'image_hidden':NDArray(cells)}) span,region,box=[torch.from_numpy(ho[k].numpy().copy()) for k in self.manifest['head_outputs']] head_results[i]=span[:,:len(offsets)] if q['type']=='span' else torch.cat([region,box]) if self.manifest['has_box'] else region return {'flat':flat,'owner':owner,'at':at,'extra':extra,'logits':logits[0],'heads':head_results,'tokens':n,'hidden':hidden[0]} async def predict(self,state,questions,images=()): from amos.decoder import options,decode_head raw=await self.raw(state,questions,images);out=[None]*len(questions) for j,(q,k,pos) in enumerate(zip(raw['flat'],raw['owner'],raw['at'])): typ=questions[k]['type'] if q['type'] in ['span','region']: out[k]=decode_head(q,raw['heads'][j],raw['extra'],state,self.manifest['keep'],self.span);continue logits=torch.tensor(raw['logits'][pos,:len(options(q))]);p=(logits/self.temperature.get(q['type'],1)).softmax(-1).tolist() if typ=='multi':out[k]=out[k] or {'type':typ,'probs':[]};out[k]['probs'].append(p[0]) elif typ=='noul':out[k]={'type':typ,'p_yes':p[0]} else: out[k]={'type':typ,'probs':p,'choice':int(np.argmax(p))} if typ=='score':out[k]['expected']=sum(i*v for i,v in enumerate(p)) return out async def request(self,req): from amos.serve import parse,answer from PIL import Image import base64,io def image(s):return Image.open(io.BytesIO(base64.b64decode(s.split(',')[-1]))).convert('RGB') state,ids,qs=parse(req);ims=[image(s) for s in req.get('images',[])] qs=[{**q,'options':[image(o['image']) if isinstance(o,dict) else o for o in q['options']]} if 'options' in q else q for q in qs] rs=await self.predict(state,qs,ims) return {'answers':{i:answer(q,r) for i,q,r in zip(ids,qs,rs)}}