46 lines
3.7 KiB
Python
46 lines
3.7 KiB
Python
#!/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()
|