#!/usr/bin/env python3 """Serve native AltAILIF inference; no host dense layers. Official and KNP CPU use same graph.""" from http.server import SimpleHTTPRequestHandler,ThreadingHTTPServer from functools import partial from pathlib import Path import json,os,subprocess,tempfile,time,threading,sys ROOT=Path('/home/user/run/mnist-altai-native');VIZ=Path('/home/user/KNP/viz');EXE=Path('/home/user/run/altai-official-probe/native_digits');LOCK=threading.BoundedSemaphore(1) class Handler(SimpleHTTPRequestHandler): extensions_map={**SimpleHTTPRequestHandler.extensions_map,'.html':'text/html; charset=utf-8','.js':'text/javascript; charset=utf-8','.json':'application/json; charset=utf-8'} def reply(self,status,data): payload=json.dumps(data,separators=(',',':')).encode();self.send_response(status);self.send_header('Content-Type','application/json; charset=utf-8');self.send_header('Content-Length',str(len(payload)));self.send_header('Cache-Control','no-store');self.end_headers() try:self.wfile.write(payload) except (BrokenPipeError,ConnectionResetError):pass def do_POST(self): if self.path!='/api/native-recognize':return self.reply(404,{'error':'Unknown endpoint'}) try: size=int(self.headers.get('Content-Length','0')) if not 1<=size<=16384:raise ValueError('Request size must be 1..16384 bytes') obj=json.loads(self.rfile.read(size));pixels=obj.get('pixels');backend=obj.get('backend','cpu') if backend not in ('cpu','gm'):raise ValueError('Backend must be cpu or gm') if not isinstance(pixels,list) or len(pixels)!=784 or any(type(v)is not int or not 0<=v<=255 for v in pixels):raise ValueError('Expected 784 integer pixels 0..255') except (ValueError,TypeError,AttributeError) as e:return self.reply(400,{'error':str(e)}) if sum(pixels)<255:return self.reply(200,dict(blank=True,prediction=None,scores=[],counts=[],backend=backend,milliseconds=0)) if not LOCK.acquire(blocking=False):return self.reply(429,{'error':'Распознавание уже выполняется. Повтори через секунду.'}) start=time.monotonic() try: with tempfile.TemporaryDirectory(prefix='request-',dir=ROOT/'web') as tmp: tmp=Path(tmp);(tmp/'model.bin').symlink_to(ROOT/'model.bin');(tmp/'labels.bin').symlink_to(ROOT/'labels.bin');(tmp/'images.bin').write_bytes(bytes(pixels));out=tmp/'result' cmd=[str(EXE),str(tmp),str(out),'0','1','1',backend,'-'] with (tmp/'run.log').open('w') as log: run=subprocess.run(cmd,stdout=log,stderr=subprocess.STDOUT,cwd=tmp,timeout=30,check=False) if run.returncode:raise RuntimeError('Native process failed: '+(tmp/'run.log').read_text()[-600:]) import struct raw=(out/'output_counts.bin').read_bytes();hidden=(out/'hidden_counts.bin').read_bytes() if len(raw)!=160*4 or len(hidden)!=256*4:raise RuntimeError('Incomplete native counters') counts=struct.unpack('<160i',raw);hc=struct.unpack('<256i',hidden) scores=[sum(counts[g*10+k]-counts[80+g*10+k] for g in range(8)) for k in range(10)];pred=max(range(10),key=lambda k:scores[k]) row=(out/'predictions.csv').read_text().splitlines()[1].split(',') if int(row[3])!=pred:raise RuntimeError('Native result decode mismatch') self.reply(200,dict(blank=False,prediction=pred,scores=scores,counts=list(hc),output_counts=list(counts),backend=backend,milliseconds=1000*(time.monotonic()-start),model='AltAILIF-native-rate32-opponent160')) except (OSError,RuntimeError,subprocess.TimeoutExpired) as e:self.reply(503,{'error':str(e)}) finally:LOCK.release() if __name__=='__main__': (ROOT/'web').mkdir(exist_ok=True) with ThreadingHTTPServer(('0.0.0.0',8081),partial(Handler,directory=str(VIZ))) as server: print('Native AltAI demo http://0.0.0.0:8081/native_viz.html',flush=True);server.serve_forever()