# Runs inside a Kaggle kernel (CPU ok). Inputs: /kaggle/input//generator_chat.pt ; outputs ncm_fp16w.onnx + report. import os, sys, json, types, time os.environ["NCM_ROOT"]="/kaggle/working/w/ncm" import torch, torch.nn as nn, numpy as np, ncmchat as N import torch.nn.functional as _F _orig_interp=_F.interpolate def _area_mat(L,o): m=np.zeros((o,L),dtype=np.float32) for i in range(o): a=(i*L)//o; b=-((-(i+1)*L)//o); m[i,a:b]=1.0/(b-a) return torch.from_numpy(m) def _lm(L,o): m=np.zeros((o,L),dtype=np.float32) for i in range(o): s=max((i+0.5)*L/o-0.5,0.0); i0=min(int(s),L-1); i1=min(i0+1,L-1); w=s-i0 m[i,i0]+=1-w; m[i,i1]+=w return torch.from_numpy(m) def _interp(x,size=None,scale_factor=None,mode="nearest",**kw): if mode=="area" and x.dim()==3 and size is not None: o=size[0] if isinstance(size,(tuple,list)) else size return torch.matmul(x,_area_mat(x.shape[-1],o).t()) if mode=="linear" and x.dim()==3 and size is not None and not kw.get("align_corners"): o=size[0] if isinstance(size,(tuple,list)) else size return torch.matmul(x,_lm(x.shape[-1],o).t()) return _orig_interp(x,size=size,scale_factor=scale_factor,mode=mode,**kw) _chk=torch.randn(2,5,48); assert torch.allclose(_orig_interp(_chk,size=(26,),mode="area"),_interp(_chk,size=(26,),mode="area"),atol=1e-5) _F.interpolate=_interp SRC=os.environ.get("SRC","ncm-chat-smoke") cands=[f"/kaggle/input/{SRC}/generator_chat.pt"]+[os.path.join(r,"generator_chat.pt") for r,_,f in os.walk("/kaggle/input") if "generator_chat.pt" in f] gp=[c for c in cands if os.path.exists(c)][0]; print("using",gp,flush=True) PEFF=int(os.environ.get("PEFF","128")) import re _c=open("ck/config.yaml").read(); _c=re.sub(r"prefix_len: 512","prefix_len: %d"%PEFF,_c); open("ck/config_p.yaml","w").write(_c) CFG,tk,net=N.load("ck",cfg_path="ck/config_p.yaml",dev="cpu",load_gen=False); net.eval() sd=torch.load(gp,map_location="cpu",weights_only=False)["model"] own=net.state_dict(); sdf={k:v.float() for k,v in sd.items() if k in own and own[k].shape==v.shape} print("skipped keys:",[k for k in sd if k not in sdf],flush=True) net.load_state_dict(sdf,strict=False); assert all(k in sdf for k in own if "attn_mask" not in k and not k.startswith("rope")), "missing weights" codec=N.Codec(CFG,tk,net); offs=[0] for l in net.levels: offs.append(offs[-1]+l) class G(nn.Module): def __init__(s): super().__init__(); s.net=net; s.vq=tk.vqvae.vq; s.dec=tk.vqvae.decoder; s.enc=tk.vqvae.encoder def forward(s, prefix, noise, temp): st={"i":0} def samp(self_, logits, temperature, top_k, top_p): i=st["i"]; st["i"]+=1 nz=noise[:, offs[i]:offs[i+1], :] v,_=torch.topk(logits,20,dim=-1) lg=torch.where(logits=3 else n.output[0] newn.append(helper.make_node("MatMul",[n.input[0],wn],[mm],name=n.name+"_mm")) if len(n.input)>=3: newn.append(helper.make_node("Add",[mm,n.input[2]],[n.output[0]],name=n.name+"_add")) ng+=1 else: newn.append(n) # remove Identity nodes fed by initializers (they hid the weights from the int8 quantizer) inits_all={i.name for i in g.initializer} alias={} final=[] for n in newn: if n.op_type=="Identity" and (n.input[0] in inits_all or n.input[0] in alias): alias[n.output[0]]=n.input[0] else: final.append(n) def res(x): while x in alias: x=alias[x] return x for n in final: for j,x in enumerate(n.input): n.input[j]=res(x) for o in g.output: if o.name in alias: raise SystemExit("identity feeds graph output") del g.node[:]; g.node.extend(final); print("gemm->matmul",ng,"identity removed",len(alias),flush=True) f32m="/kaggle/working/ncm_fp32_mm.onnx"; onnx.save(mo,f32m) i8="/kaggle/working/out/ncm_fp16w.onnx" # weights stored fp16 + Cast->fp32 at load (compute stays fp32) mo=onnx.load(f32m); g=mo.graph; casts=[]; nconv=0 for init in list(g.initializer): if init.data_type==onnx.TensorProto.FLOAT and int(np.prod(init.dims))>=1024: arr=numpy_helper.to_array(init); h=numpy_helper.from_array(arr.astype(np.float16),init.name+"_h") casts.append(helper.make_node("Cast",[init.name+"_h"],[init.name],to=onnx.TensorProto.FLOAT,name=init.name+"_cast")) g.initializer.remove(init); g.initializer.append(h); nconv+=1 nodes=list(g.node); del g.node[:]; g.node.extend(casts+nodes) onnx.save(mo,i8); print("fp16-weights onnx MB",os.path.getsize(i8)/1e6,"converted",nconv,flush=True) res={"files_MB":{"fp32":os.path.getsize(f32)/1e6,"fp16w":os.path.getsize(i8)/1e6},"cases":[]} for name,path in [("fp32",f32m),("fp16w",i8)]: s=ort.InferenceSession(path,providers=["CPUExecutionProvider"]); outs=[] t=time.time() for i in range(len(tests)): o=s.run(None,{"prefix":pre[i:i+1].numpy(),"noise":Z.numpy(),"temp":np.array(0.0,dtype=np.float32)})[0][0].tolist() outs.append(o) dt=(time.time()-t)/len(tests); res[name]={"sec_per_reply_cpu":dt,"exact_match_vs_torch":sum(o==r for o,r in zip(outs,ref)),"replies":[txt(o) for o in outs]} print(name,"exact-match",res[name]["exact_match_vs_torch"],"/",len(tests),"s/reply",round(dt,2),flush=True) for t_,o in zip(tests,outs): print(" ",t_,"->",txt(o),flush=True) res["torch_replies"]=[txt(r) for r in ref] # tokenizer parity vectors for the browser res["tok_vectors"]=[{"prompt":p,"ids":codec.enc(p),"prefix_ids":codec.prefix_ids(p)[:len(codec.enc(p))]} for p in prompts[:4]] res["EOS"]=codec.EOS; res["PAD"]=codec.PAD; res["ppad"]=codec.ppad; res["P"]=codec.P; res["offs"]=offs json.dump(res,open("/kaggle/working/out/report.json","w"),indent=1) print("done",flush=True)