danielharkin21 commited on
Commit
951ac76
·
verified ·
1 Parent(s): e2e69a1

Upload folder using huggingface_hub

Browse files
README.md ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ license: mit
3
+ tags:
4
+ - next-concept-modeling
5
+ - ncm
6
+ - experimental
7
+ ---
8
+
9
+ # NCM chat generator (experimental, rough)
10
+
11
+ Fine-tuned generator weights (fp16, 177 MB) for a small chat demo built on **Next-Concept Modeling** (Stanford Hazy Research, arXiv 2610.14162, https://github.com/HazyResearch/ncm).
12
+
13
+ - Base: the released XSum checkpoint at https://huggingface.co/hazyresearch/ncm-checkpoints (frozen multiscale VQ tokenizer plus prefix-conditioned generator). Only the generator was fine-tuned, for 4000 steps on short public dialogue and instruction data (everyday-conversations, dolly, alpaca; 21.6k examples). Held-out code loss 6.31 to 1.47.
14
+ - Load it with the base repo's config, tokenizer and code (`code/ncmchat.py` shows how). `generator_chat.pt` holds a `model` state dict.
15
+ - Prefix window 128 tokens, reply up to 48 tokens, levels [4,6,10,14,20,26,32,40,48].
16
+ - `code/` has the training, ONNX export and parity scripts.
17
+ - Quality is rough: replies are short and often repetitive or wrong. This is a research demo, not an assistant.
18
+ - Browser demo: https://huggingface.co/spaces/danielharkin21/ncm-chat-experimental
code/diag2_body.py ADDED
@@ -0,0 +1,110 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Teacher-forced logit parity: identical prefix + identical codes through torch and ONNX (fp32, fp32 no-opt, int8).
2
+ import os, sys, json, types, re, time
3
+ os.environ["NCM_ROOT"]="/kaggle/working/w/ncm"
4
+ import torch, torch.nn as nn, numpy as np, ncmchat as N
5
+ import torch.nn.functional as _F
6
+ _orig=_F.interpolate
7
+ def _am(L,o):
8
+ m=np.zeros((o,L),dtype=np.float32)
9
+ for i in range(o):
10
+ a=(i*L)//o; b=-((-(i+1)*L)//o); m[i,a:b]=1.0/(b-a)
11
+ return torch.from_numpy(m)
12
+ def _lm(L,o):
13
+ m=np.zeros((o,L),dtype=np.float32)
14
+ for i in range(o):
15
+ 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
16
+ m[i,i0]+=1-w; m[i,i1]+=w
17
+ return torch.from_numpy(m)
18
+ def _interp(x,size=None,scale_factor=None,mode="nearest",**kw):
19
+ if mode=="area" and x.dim()==3 and size is not None:
20
+ o=size[0] if isinstance(size,(tuple,list)) else size
21
+ return torch.matmul(x,_am(x.shape[-1],o).t())
22
+ if mode=="linear" and x.dim()==3 and size is not None and not kw.get("align_corners"):
23
+ o=size[0] if isinstance(size,(tuple,list)) else size
24
+ return torch.matmul(x,_lm(x.shape[-1],o).t())
25
+ return _orig(x,size=size,scale_factor=scale_factor,mode=mode,**kw)
26
+ gp=[os.path.join(r,"generator_chat.pt") for r,_,f in os.walk("/kaggle/input") if "generator_chat.pt" in f][0]
27
+ P=128
28
+ c=open("ck/config.yaml").read(); c=re.sub(r"prefix_len: 512","prefix_len: %d"%P,c); open("ck/config_p.yaml","w").write(c)
29
+ CFG,tk,net=N.load("ck",cfg_path="ck/config_p.yaml",dev="cpu",load_gen=False); net.eval()
30
+ sd=torch.load(gp,map_location="cpu",weights_only=False)["model"]; own=net.state_dict()
31
+ net.load_state_dict({k:v.float() for k,v in sd.items() if k in own and own[k].shape==v.shape},strict=False)
32
+ codec=N.Codec(CFG,tk,net); offs=[0]
33
+ for l in net.levels: offs.append(offs[-1]+l)
34
+ levels=list(net.levels)
35
+ class GTF(nn.Module): # generate-path with forced codes; returns logits of every level
36
+ def __init__(s):
37
+ super().__init__(); s.net=net
38
+ def forward(s,prefix,codes):
39
+ st={"i":0}; L=[]
40
+ def samp(self_,logits,temperature,top_k,top_p):
41
+ i=st["i"]; st["i"]+=1; L.append(logits)
42
+ return codes[:,offs[i]:offs[i+1]]
43
+ s.net._sample_level=types.MethodType(samp,s.net)
44
+ s.net.generate(None,prefix,temperature=1.0,top_k=20,top_p=0.9)
45
+ return torch.cat(L,1)
46
+ m=GTF().eval()
47
+ tests=["Hi, how are you?","What is the capital of France?","Tell me a joke.","What do you like to do for fun?","Can you recommend a good book?","How do I boil an egg?","I'm feeling sad today.","What is the meaning of life?"]
48
+ prompts=[N.fmt_prompt([("user",t)]) for t in tests]
49
+ pre=torch.tensor([codec.prefix_ids(p) for p in prompts])
50
+ # codes: use the REAL data codes of plausible replies (teacher forcing), via tokenizer encode
51
+ reps=["Hello! How can I help you today?","The capital of France is Paris.","Why did the chicken cross the road? To get to the other side.","I like to read books and go hiking.","I recommend The Hobbit, it is a fun story.","Put it in boiling water for ten minutes.","I am sorry to hear that. I hope you feel better.","Life is about finding what makes you happy."]
52
+ tgt=torch.tensor([codec.target_ids(r) for r in reps])
53
+ with torch.no_grad(): codes=tk.vqvae.encode(tgt)
54
+ # (1) training-path forward logits vs generate-path logits (torch vs torch)
55
+ with torch.no_grad():
56
+ lg_gen=torch.cat([m(pre[i:i+1],codes[i:i+1]) for i in range(len(tests))],0)
57
+ x=N.build_inputs(codec,pre,codes,0.0); pm=pre==codec.ppad
58
+ lg_tr=net(x,prefix_pad_mask=pm)
59
+ d_tr=(lg_gen-lg_tr).abs()
60
+ print("torch generate-path vs training-forward logits: max|d|",float(d_tr.max()),"mean|d|",float(d_tr.mean()),"argmax mismatch",int((lg_gen.argmax(-1)!=lg_tr.argmax(-1)).sum()),"/",lg_gen.shape[0]*lg_gen.shape[1],flush=True)
61
+ os.makedirs("/kaggle/working/out",exist_ok=True)
62
+ _F.interpolate=_interp
63
+ torch.onnx.export(m,(pre[:1],codes[:1]),"/kaggle/working/tf.onnx",input_names=["prefix","codes"],output_names=["logits"],opset_version=17,dynamo=False)
64
+ import onnxruntime as ort, onnx
65
+ from onnx import numpy_helper, helper
66
+ def clean_quant(src,dst):
67
+ mo=onnx.load(src); g=mo.graph
68
+ keep=[]
69
+ for n in g.node: # Constant nodes -> initializers so the quantizer sees weights
70
+ if n.op_type=="Constant" and len(n.attribute)==1 and n.attribute[0].name=="value":
71
+ t=n.attribute[0].t; t.name=n.output[0]; g.initializer.append(t)
72
+ else: keep.append(n)
73
+ del g.node[:]; g.node.extend(keep)
74
+ inits={i.name for i in g.initializer}; alias={}; final=[]
75
+ for n in g.node:
76
+ if n.op_type=="Identity" and (n.input[0] in inits or n.input[0] in alias): alias[n.output[0]]=n.input[0]
77
+ else: final.append(n)
78
+ def r(x):
79
+ while x in alias: x=alias[x]
80
+ return x
81
+ for n in final:
82
+ for j,xx in enumerate(n.input): n.input[j]=r(xx)
83
+ del g.node[:]; g.node.extend(final); onnx.save(mo,"/kaggle/working/tf_clean.onnx")
84
+ from onnxruntime.quantization import quantize_dynamic, QuantType
85
+ quantize_dynamic("/kaggle/working/tf_clean.onnx",dst,weight_type=QuantType.QInt8)
86
+ HAVE_I8=True
87
+ try:
88
+ clean_quant("/kaggle/working/tf.onnx","/kaggle/working/tf_i8.onnx"); print("int8 MB",os.path.getsize("/kaggle/working/tf_i8.onnx")/1e6,flush=True)
89
+ except Exception as e:
90
+ HAVE_I8=False; print("INT8 QUANT FAILED",repr(e)[:300],flush=True)
91
+ def run(path,opt):
92
+ so=ort.SessionOptions(); so.graph_optimization_level=opt
93
+ s=ort.InferenceSession(path,so,providers=["CPUExecutionProvider"])
94
+ return torch.cat([torch.from_numpy(s.run(None,{"prefix":pre[i:i+1].numpy(),"codes":codes[i:i+1].numpy()})[0]) for i in range(len(tests))],0)
95
+ rep={}
96
+ for name,path,opt in [("onnx_fp32_default","/kaggle/working/tf.onnx",ort.GraphOptimizationLevel.ORT_ENABLE_ALL),("onnx_fp32_noopt","/kaggle/working/tf.onnx",ort.GraphOptimizationLevel.ORT_DISABLE_ALL),("onnx_int8","/kaggle/working/tf_i8.onnx",ort.GraphOptimizationLevel.ORT_ENABLE_ALL)]:
97
+ if name=="onnx_int8" and not HAVE_I8: continue
98
+ lg=run(path,opt); d=(lg-lg_gen).abs(); top2=lg_gen.topk(2,-1).values; gap=top2[...,0]-top2[...,1]
99
+ mism=(lg.argmax(-1)!=lg_gen.argmax(-1))
100
+ per=[]
101
+ for k in range(len(levels)):
102
+ a,b=offs[k],offs[k+1]
103
+ per.append({"level":k,"max_abs":float(d[:,a:b].max()),"mean_abs":float(d[:,a:b].mean()),"argmax_mismatch":int(mism[:,a:b].sum()),"of":int(mism[:,a:b].numel())})
104
+ rep[name]={"max_abs":float(d.max()),"mean_abs":float(d.mean()),"logit_std":float(lg_gen.std()),"argmax_mismatch":int(mism.sum()),"of":int(mism.numel()),
105
+ "gap_at_mismatches_max":float(gap[mism].max()) if mism.any() else None,"gap_at_mismatches_median":float(gap[mism].median()) if mism.any() else None,"per_level":per}
106
+ print(name,"max|d|",rep[name]["max_abs"],"mean|d|",rep[name]["mean_abs"],"logit std",rep[name]["logit_std"],"argmax mism",rep[name]["argmax_mismatch"],"/",rep[name]["of"],"gap@mism max/med",rep[name]["gap_at_mismatches_max"],rep[name]["gap_at_mismatches_median"],flush=True)
107
+ for p_ in per: print(" ",p_,flush=True)
108
+ rep["torch_gen_vs_train_forward"]={"max_abs":float(d_tr.max()),"mean_abs":float(d_tr.mean())}
109
+ json.dump(rep,open("/kaggle/working/out/parity.json","w"),indent=1)
110
+ print("done",flush=True)
code/export_onnx_body.py ADDED
@@ -0,0 +1,127 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Runs inside a Kaggle kernel (CPU ok). Inputs: /kaggle/input/<SRC>/generator_chat.pt ; outputs ncm_fp16w.onnx + report.
2
+ import os, sys, json, types, time
3
+ os.environ["NCM_ROOT"]="/kaggle/working/w/ncm"
4
+ import torch, torch.nn as nn, numpy as np, ncmchat as N
5
+ import torch.nn.functional as _F
6
+ _orig_interp=_F.interpolate
7
+ def _area_mat(L,o):
8
+ m=np.zeros((o,L),dtype=np.float32)
9
+ for i in range(o):
10
+ a=(i*L)//o; b=-((-(i+1)*L)//o); m[i,a:b]=1.0/(b-a)
11
+ return torch.from_numpy(m)
12
+ def _lm(L,o):
13
+ m=np.zeros((o,L),dtype=np.float32)
14
+ for i in range(o):
15
+ 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
16
+ m[i,i0]+=1-w; m[i,i1]+=w
17
+ return torch.from_numpy(m)
18
+ def _interp(x,size=None,scale_factor=None,mode="nearest",**kw):
19
+ if mode=="area" and x.dim()==3 and size is not None:
20
+ o=size[0] if isinstance(size,(tuple,list)) else size
21
+ return torch.matmul(x,_area_mat(x.shape[-1],o).t())
22
+ if mode=="linear" and x.dim()==3 and size is not None and not kw.get("align_corners"):
23
+ o=size[0] if isinstance(size,(tuple,list)) else size
24
+ return torch.matmul(x,_lm(x.shape[-1],o).t())
25
+ return _orig_interp(x,size=size,scale_factor=scale_factor,mode=mode,**kw)
26
+ _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)
27
+ _F.interpolate=_interp
28
+ SRC=os.environ.get("SRC","ncm-chat-smoke")
29
+ 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]
30
+ gp=[c for c in cands if os.path.exists(c)][0]; print("using",gp,flush=True)
31
+ PEFF=int(os.environ.get("PEFF","128"))
32
+ import re
33
+ _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)
34
+ CFG,tk,net=N.load("ck",cfg_path="ck/config_p.yaml",dev="cpu",load_gen=False); net.eval()
35
+ sd=torch.load(gp,map_location="cpu",weights_only=False)["model"]
36
+ own=net.state_dict(); sdf={k:v.float() for k,v in sd.items() if k in own and own[k].shape==v.shape}
37
+ print("skipped keys:",[k for k in sd if k not in sdf],flush=True)
38
+ 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"
39
+ codec=N.Codec(CFG,tk,net); offs=[0]
40
+ for l in net.levels: offs.append(offs[-1]+l)
41
+ class G(nn.Module):
42
+ def __init__(s):
43
+ super().__init__(); s.net=net; s.vq=tk.vqvae.vq; s.dec=tk.vqvae.decoder; s.enc=tk.vqvae.encoder
44
+ def forward(s, prefix, noise, temp):
45
+ st={"i":0}
46
+ def samp(self_, logits, temperature, top_k, top_p):
47
+ i=st["i"]; st["i"]+=1
48
+ nz=noise[:, offs[i]:offs[i+1], :]
49
+ v,_=torch.topk(logits,20,dim=-1)
50
+ lg=torch.where(logits<v[...,-1:],torch.full_like(logits,-1e9),logits)
51
+ return (lg+temp*nz).argmax(-1)
52
+ s.net._sample_level=types.MethodType(samp,s.net)
53
+ g=s.net.generate(None,prefix,temperature=1.0,top_k=20,top_p=0.9)
54
+ _,zl=s.vq.idx_to_zhats(g,return_zlevels=True)
55
+ return s.dec(zl[-1]).argmax(-1)
56
+ m=G().eval()
57
+ tests=["Hi, how are you?","What is the capital of France?","Tell me a joke.","What do you like to do for fun?","Can you recommend a good book?","How do I boil an egg?","I'm feeling sad today.","What is the meaning of life?"]
58
+ prompts=[N.fmt_prompt([("user",t)]) for t in tests]
59
+ pre=torch.tensor([codec.prefix_ids(p) for p in prompts])
60
+ Z=torch.zeros(1,offs[-1],4096); T0=torch.tensor(0.0)
61
+ def txt(ids):
62
+ ids=list(ids)
63
+ if codec.EOS in ids: ids=ids[:ids.index(codec.EOS)]
64
+ return codec.pre.decode([t for t in ids if t not in (codec.EOS,codec.PAD)],skip_special_tokens=True).strip()
65
+ with torch.no_grad(): ref=[m(pre[i:i+1],Z,T0)[0].tolist() for i in range(len(tests))]
66
+ print("torch greedy:",[txt(r) for r in ref],flush=True)
67
+ os.makedirs("/kaggle/working/out",exist_ok=True)
68
+ f32="/kaggle/working/ncm_fp32.onnx"
69
+ with torch.no_grad(): torch.onnx.export(m,(pre[:1],Z,T0),f32,input_names=["prefix","noise","temp"],output_names=["ids"],opset_version=17,dynamo=False)
70
+ print("fp32 onnx MB",os.path.getsize(f32)/1e6,flush=True)
71
+ import onnxruntime as ort
72
+ from onnxruntime.quantization import quantize_dynamic, QuantType
73
+ import onnx
74
+ from onnx import numpy_helper, helper
75
+ mo=onnx.load(f32); g=mo.graph; inits={i.name:i for i in g.initializer}; newn=[]; ng=0
76
+ for n in g.node:
77
+ if n.op_type=="Gemm" and n.input[1] in inits and (len(n.input)<3 or n.input[2] in inits):
78
+ at={a.name:helper.get_attribute_value(a) for a in n.attribute}
79
+ W=numpy_helper.to_array(inits[n.input[1]]); W=(W.T if at.get("transB",0) else W)*at.get("alpha",1.0)
80
+ wn=n.input[1]+"_mm"; g.initializer.append(numpy_helper.from_array(W.astype("float32"),wn))
81
+ mm=n.output[0]+"_mmout" if len(n.input)>=3 else n.output[0]
82
+ newn.append(helper.make_node("MatMul",[n.input[0],wn],[mm],name=n.name+"_mm"))
83
+ if len(n.input)>=3: newn.append(helper.make_node("Add",[mm,n.input[2]],[n.output[0]],name=n.name+"_add"))
84
+ ng+=1
85
+ else: newn.append(n)
86
+ # remove Identity nodes fed by initializers (they hid the weights from the int8 quantizer)
87
+ inits_all={i.name for i in g.initializer}
88
+ alias={}
89
+ final=[]
90
+ for n in newn:
91
+ if n.op_type=="Identity" and (n.input[0] in inits_all or n.input[0] in alias):
92
+ alias[n.output[0]]=n.input[0]
93
+ else: final.append(n)
94
+ def res(x):
95
+ while x in alias: x=alias[x]
96
+ return x
97
+ for n in final:
98
+ for j,x in enumerate(n.input): n.input[j]=res(x)
99
+ for o in g.output:
100
+ if o.name in alias: raise SystemExit("identity feeds graph output")
101
+ del g.node[:]; g.node.extend(final); print("gemm->matmul",ng,"identity removed",len(alias),flush=True)
102
+ f32m="/kaggle/working/ncm_fp32_mm.onnx"; onnx.save(mo,f32m)
103
+ i8="/kaggle/working/out/ncm_fp16w.onnx" # weights stored fp16 + Cast->fp32 at load (compute stays fp32)
104
+ mo=onnx.load(f32m); g=mo.graph; casts=[]; nconv=0
105
+ for init in list(g.initializer):
106
+ if init.data_type==onnx.TensorProto.FLOAT and int(np.prod(init.dims))>=1024:
107
+ arr=numpy_helper.to_array(init); h=numpy_helper.from_array(arr.astype(np.float16),init.name+"_h")
108
+ casts.append(helper.make_node("Cast",[init.name+"_h"],[init.name],to=onnx.TensorProto.FLOAT,name=init.name+"_cast"))
109
+ g.initializer.remove(init); g.initializer.append(h); nconv+=1
110
+ nodes=list(g.node); del g.node[:]; g.node.extend(casts+nodes)
111
+ onnx.save(mo,i8); print("fp16-weights onnx MB",os.path.getsize(i8)/1e6,"converted",nconv,flush=True)
112
+ res={"files_MB":{"fp32":os.path.getsize(f32)/1e6,"fp16w":os.path.getsize(i8)/1e6},"cases":[]}
113
+ for name,path in [("fp32",f32m),("fp16w",i8)]:
114
+ s=ort.InferenceSession(path,providers=["CPUExecutionProvider"]); outs=[]
115
+ t=time.time()
116
+ for i in range(len(tests)):
117
+ o=s.run(None,{"prefix":pre[i:i+1].numpy(),"noise":Z.numpy(),"temp":np.array(0.0,dtype=np.float32)})[0][0].tolist()
118
+ outs.append(o)
119
+ 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]}
120
+ print(name,"exact-match",res[name]["exact_match_vs_torch"],"/",len(tests),"s/reply",round(dt,2),flush=True)
121
+ for t_,o in zip(tests,outs): print(" ",t_,"->",txt(o),flush=True)
122
+ res["torch_replies"]=[txt(r) for r in ref]
123
+ # tokenizer parity vectors for the browser
124
+ res["tok_vectors"]=[{"prompt":p,"ids":codec.enc(p),"prefix_ids":codec.prefix_ids(p)[:len(codec.enc(p))]} for p in prompts[:4]]
125
+ res["EOS"]=codec.EOS; res["PAD"]=codec.PAD; res["ppad"]=codec.ppad; res["P"]=codec.P; res["offs"]=offs
126
+ json.dump(res,open("/kaggle/working/out/report.json","w"),indent=1)
127
+ print("done",flush=True)
code/ncm_chat_train.py ADDED
@@ -0,0 +1,102 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os, subprocess, sys, json, random, time, math
2
+ STEPS=int(os.environ.get("STEPS","300")); BS=int(os.environ.get("BS","24")); LR=float(os.environ.get("LR","1e-4"))
3
+ def sh(c): print("$",c,flush=True); subprocess.run(c,shell=True,check=True)
4
+ os.makedirs("/kaggle/working/w",exist_ok=True); os.chdir("/kaggle/working/w")
5
+ if not os.path.exists("ncm"): sh("git clone -q https://github.com/HazyResearch/ncm.git")
6
+ sh("pip install -q omegaconf hydra-core regex 2>&1 | tail -1")
7
+ os.makedirs("ck",exist_ok=True)
8
+ for f in ["config.yaml","generator.pt","tokenizer.pt"]:
9
+ if not os.path.exists("ck/"+f): sh(f"curl -sL -o ck/{f} https://huggingface.co/hazyresearch/ncm-checkpoints/resolve/main/xsum/{f}")
10
+ open('ncmchat.py','w').write('"""NCM chat fine-tune: reuse released XSum NCM (frozen multiscale VQ tokenizer + prefix-conditioned generator),\nfine-tune the generator on (conversation prefix -> short reply). Needs HazyResearch/ncm on NCM_ROOT."""\nimport os, sys, random, torch, torch.nn.functional as F\nfrom omegaconf import OmegaConf\nNCM_ROOT = os.environ.get("NCM_ROOT", "/tmp/ncm"); sys.path.insert(0, NCM_ROOT)\nfrom models.next_concept import NextConceptTransformer\nfrom tokenization.vqvae_tokenizer import VQVAETokenizer\n\ndef load(ck, cfg_path=None, dev="cpu", load_gen=True):\n CFG = OmegaConf.load(cfg_path or os.path.join(ck, "config.yaml"))\n CFG.tokenizer.vqvae.checkpoint_path = os.path.join(ck, CFG.checkpoints.tokenizer)\n tk = VQVAETokenizer.from_config(CFG.tokenizer)\n tk.vqvae = tk.vqvae.to(dev).eval()\n for p in tk.vqvae.parameters(): p.requires_grad = False\n net = NextConceptTransformer(vqvae_local=tk.vqvae, config=CFG.model).to(dev)\n if load_gen:\n sd = torch.load(os.path.join(ck, CFG.checkpoints.generator), map_location="cpu", weights_only=False)\n net.load_state_dict(sd["model"], strict=True)\n return CFG, tk, net\n\ndef fmt_prompt(turns):\n """turns: list of (role, text) ending with a user turn -> prompt string"""\n s = "".join(f"{\'User\' if r==\'user\' else \'Assistant\'}: {t}\\n" for r, t in turns)\n return s + "Assistant:"\n\nclass Codec:\n def __init__(self, CFG, tk, net):\n self.tk, self.net, self.P = tk, net, int(CFG.model.prefix_len)\n self.pre = tk.pre_tokenizer\n self.EOS, self.PAD = self.pre.eos_token_id, self.pre.pad_token_id\n self.ppad = int(CFG.model.prefix_pad_token_id) # pad id used in the generator prefix\n self.T = tk.vqvae.context_length # target length (48)\n self.hf = self.pre.tokenizer if hasattr(self.pre, "tokenizer") else None\n def enc(self, text): return self.pre.encode(text)\n def prefix_ids(self, prompt):\n ids = self.enc(prompt)[-self.P:] # keep most recent context\n return ids + [self.ppad] * (self.P - len(ids))\n def target_ids(self, reply):\n ids = self.enc(" " + reply.strip())[: self.T - 1] + [self.EOS]\n return ids + [self.EOS] * (self.T - len(ids))\n\n@torch.no_grad()\ndef target_codes(codec, tgt): # tgt: LongTensor (B,T) -> idx (B, sum levels)\n return codec.tk.vqvae.encode(tgt)\n\ndef build_inputs(codec, prefix, idx, corrupt=0.1):\n net, vq = codec.net, codec.tk.vqvae.vq\n with torch.no_grad():\n idc = idx.clone()\n if corrupt > 0: # robustness: corrupt conditioning codes (as in paper)\n m = torch.rand_like(idc, dtype=torch.float) < corrupt\n idc[m] = torch.randint(0, 4096, (int(m.sum()),), device=idc.device)\n nc = vq.idx_to_ncm_input(idc) # cumulative recon fed to levels 1..K-1\n zp = codec.tk.vqvae.encoder.wte(prefix)[:, :codec.P, :]\n q0 = net.build_l0_query_input(prefix.shape[0])\n return torch.cat([zp, q0, nc], dim=1)\n\ndef loss_fn(codec, prefix, tgt, corrupt=0.1):\n idx = target_codes(codec, tgt)\n x = build_inputs(codec, prefix, idx, corrupt)\n pad_mask = prefix == codec.ppad\n logits = codec.net(x, prefix_pad_mask=None if not hasattr(codec.net, "_prefix_pad_bias") else pad_mask)\n return F.cross_entropy(logits.reshape(-1, logits.shape[-1]).float(), idx.reshape(-1)), logits, idx\n\n@torch.no_grad()\ndef reply(codec, prompts, temperature=0.8, top_k=20, top_p=0.9, dev="cpu"):\n net, tk = codec.net, codec.tk\n pre = torch.tensor([codec.prefix_ids(p) for p in prompts], device=dev)\n g = net.generate(None, pre, temperature=temperature, top_k=top_k, top_p=top_p)\n _, zl = tk.vqvae.vq.idx_to_zhats(g, return_zlevels=True)\n ids = tk.vqvae.decoder(zl[-1]).argmax(-1).tolist()\n out = []\n for r in ids:\n if codec.EOS in r: r = r[: r.index(codec.EOS)]\n out.append(codec.pre.decode([t for t in r if t not in (codec.EOS, codec.PAD)], skip_special_tokens=True).strip())\n return out\n')
11
+ sys.path.insert(0,os.getcwd())
12
+ os.environ["NCM_ROOT"]="/kaggle/working/w/ncm"
13
+ import torch, ncmchat as N
14
+ dev="cuda"
15
+ CFG,tk,net=N.load("ck",dev=dev); codec=N.Codec(CFG,tk,net)
16
+ # ---------------- data ----------------
17
+ random.seed(0)
18
+ from datasets import load_dataset
19
+ ex=[] # (turns list[(role,text)], reply)
20
+ def add_dialog(utts, maxn=6):
21
+ # utts alternating, first is "user"
22
+ for i in range(1,len(utts)):
23
+ if i%2==1:
24
+ hist=[("user" if j%2==0 else "assistant",utts[j]) for j in range(max(0,i-3),i)]
25
+ if hist[0][0]=="assistant": hist=hist[1:]
26
+ ex.append((hist,utts[i]))
27
+ src={}
28
+ def tryload(name,fn):
29
+ try:
30
+ n0=len(ex); fn(); src[name]=len(ex)-n0; print("loaded",name,src[name],flush=True)
31
+ except Exception as e: print("FAILED",name,repr(e)[:200],flush=True)
32
+ def dd():
33
+ d=load_dataset("roskoN/dailydialog",split="train")
34
+ for r in d:
35
+ u=r.get("utterances") or r.get("dialog")
36
+ add_dialog([x.strip() for x in u])
37
+ def ev():
38
+ d=load_dataset("HuggingFaceTB/everyday-conversations-llama3.1-2k",split="train_sft")
39
+ for r in d:
40
+ m=r["messages"]; add_dialog([x["content"].strip() for x in m if x["role"]!="system"])
41
+ def dolly():
42
+ d=load_dataset("databricks/databricks-dolly-15k",split="train")
43
+ for r in d:
44
+ if not r["context"] and r["response"]: ex.append(([("user",r["instruction"].strip())],r["response"].strip()))
45
+ def alp():
46
+ d=load_dataset("tatsu-lab/alpaca",split="train")
47
+ for r in d:
48
+ if not r["input"]: ex.append(([("user",r["instruction"].strip())],r["output"].strip()))
49
+ tryload("dailydialog",dd); tryload("everyday",ev); tryload("dolly",dolly); tryload("alpaca",alp)
50
+ print("raw examples",len(ex),flush=True)
51
+ def ok(e):
52
+ h,r=e
53
+ return 0<len(codec.enc(r))<=40 and "\n" not in r and len(r)>1
54
+ ex=[e for e in ex if ok(e)]
55
+ random.shuffle(ex); print("kept short-reply examples",len(ex),flush=True)
56
+ val=ex[:500]; tr=ex[500:]
57
+ def batch(items):
58
+ pre=torch.tensor([codec.prefix_ids(N.fmt_prompt(h)) for h,_ in items],device=dev)
59
+ tgt=torch.tensor([codec.target_ids(r) for _,r in items],device=dev)
60
+ return pre,tgt
61
+ # ---------------- train ----------------
62
+ opt=torch.optim.AdamW([p for p in net.parameters() if p.requires_grad],lr=LR,weight_decay=0.01,betas=(0.9,0.95))
63
+ scaler=torch.cuda.amp.GradScaler()
64
+ def evalval():
65
+ net.eval(); tot=0;n=0
66
+ with torch.no_grad():
67
+ for i in range(0,len(val),50):
68
+ pre,tgt=batch(val[i:i+50])
69
+ with torch.autocast("cuda",dtype=torch.float16): l,_,_=N.loss_fn(codec,pre,tgt,0.0)
70
+ tot+=l.item()*len(pre); n+=len(pre)
71
+ net.train(); return tot/n
72
+ net.train(); print("val loss before",evalval(),flush=True)
73
+ t0=time.time(); ptr=0; ema=None
74
+ for step in range(1,STEPS+1):
75
+ lr=LR*min(1,step/50)*(0.1+0.9*0.5*(1+math.cos(math.pi*step/STEPS)))
76
+ for g in opt.param_groups: g["lr"]=lr
77
+ if ptr+BS>len(tr): random.shuffle(tr); ptr=0
78
+ pre,tgt=batch(tr[ptr:ptr+BS]); ptr+=BS
79
+ with torch.autocast("cuda",dtype=torch.float16): loss,_,_=N.loss_fn(codec,pre,tgt,0.1)
80
+ opt.zero_grad(set_to_none=True); scaler.scale(loss).backward(); scaler.unscale_(opt)
81
+ torch.nn.utils.clip_grad_norm_(net.parameters(),1.0); scaler.step(opt); scaler.update()
82
+ ema=loss.item() if ema is None else 0.98*ema+0.02*loss.item()
83
+ if step%50==0: print(f"step {step} loss {ema:.4f} lr {lr:.2e} {time.time()-t0:.0f}s",flush=True)
84
+ if step%1000==0: print("val loss",evalval(),flush=True)
85
+ print("val loss after",evalval(),flush=True)
86
+ net.eval()
87
+ tests=["Hi, how are you?","What is the capital of France?","Tell me a joke.","What do you like to do for fun?","Can you recommend a good book?","How do I boil an egg?","I'm feeling sad today.","What is the meaning of life?"]
88
+ prompts=[N.fmt_prompt([("user",t)]) for t in tests]
89
+ res=[]
90
+ for T,K in [(0.7,20),(0.0,1)]:
91
+ with torch.autocast("cuda",dtype=torch.float16): outs=N.reply(codec,prompts,temperature=T,top_k=K,top_p=0.9,dev=dev)
92
+ for t,o in zip(tests,outs): res.append({"temp":T,"user":t,"ncm_reply":o}); print(f"[T={T}] User: {t}\n NCM: {o}",flush=True)
93
+ # speed: one batch of 1 and 8
94
+ import time as _t
95
+ for B in (1,8):
96
+ torch.cuda.synchronize(); a=_t.time()
97
+ with torch.autocast("cuda",dtype=torch.float16): N.reply(codec,prompts[:B]*1,dev=dev)
98
+ torch.cuda.synchronize(); print(f"gen time batch {B}: {_t.time()-a:.3f}s",flush=True)
99
+ json.dump({"src":src,"n_train":len(tr),"steps":STEPS,"samples":res},open("/kaggle/working/report.json","w"),indent=1)
100
+ sd={"model":{k:v.detach().cpu().half() for k,v in net.state_dict().items()}}
101
+ torch.save(sd,"/kaggle/working/generator_chat.pt")
102
+ print("saved",flush=True)
code/ncm_export_kernel.py ADDED
@@ -0,0 +1,138 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os, subprocess, sys, json, random, time, math
2
+
3
+ def sh(c): print("$",c,flush=True); subprocess.run(c,shell=True,check=True)
4
+ os.makedirs("/kaggle/working/w",exist_ok=True); os.chdir("/kaggle/working/w")
5
+ if not os.path.exists("ncm"): sh("git clone -q https://github.com/HazyResearch/ncm.git")
6
+ sh("pip install -q omegaconf hydra-core regex onnx onnxruntime onnxscript 2>&1 | tail -1")
7
+ os.makedirs("ck",exist_ok=True)
8
+ for f in ["config.yaml","generator.pt","tokenizer.pt"]:
9
+ if not os.path.exists("ck/"+f): sh(f"curl -sL -o ck/{f} https://huggingface.co/hazyresearch/ncm-checkpoints/resolve/main/xsum/{f}")
10
+ open('ncmchat.py','w').write('"""NCM chat fine-tune: reuse released XSum NCM (frozen multiscale VQ tokenizer + prefix-conditioned generator),\nfine-tune the generator on (conversation prefix -> short reply). Needs HazyResearch/ncm on NCM_ROOT."""\nimport os, sys, random, torch, torch.nn.functional as F\nfrom omegaconf import OmegaConf\nNCM_ROOT = os.environ.get("NCM_ROOT", "/tmp/ncm"); sys.path.insert(0, NCM_ROOT)\nfrom models.next_concept import NextConceptTransformer\nfrom tokenization.vqvae_tokenizer import VQVAETokenizer\n\ndef load(ck, cfg_path=None, dev="cpu", load_gen=True):\n CFG = OmegaConf.load(cfg_path or os.path.join(ck, "config.yaml"))\n CFG.tokenizer.vqvae.checkpoint_path = os.path.join(ck, CFG.checkpoints.tokenizer)\n tk = VQVAETokenizer.from_config(CFG.tokenizer)\n tk.vqvae = tk.vqvae.to(dev).eval()\n for p in tk.vqvae.parameters(): p.requires_grad = False\n net = NextConceptTransformer(vqvae_local=tk.vqvae, config=CFG.model).to(dev)\n if load_gen:\n sd = torch.load(os.path.join(ck, CFG.checkpoints.generator), map_location="cpu", weights_only=False)\n net.load_state_dict(sd["model"], strict=True)\n return CFG, tk, net\n\ndef fmt_prompt(turns):\n """turns: list of (role, text) ending with a user turn -> prompt string"""\n s = "".join(f"{\'User\' if r==\'user\' else \'Assistant\'}: {t}\\n" for r, t in turns)\n return s + "Assistant:"\n\nclass Codec:\n def __init__(self, CFG, tk, net):\n self.tk, self.net, self.P = tk, net, int(CFG.model.prefix_len)\n self.pre = tk.pre_tokenizer\n self.EOS, self.PAD = self.pre.eos_token_id, self.pre.pad_token_id\n self.ppad = int(CFG.model.prefix_pad_token_id) # pad id used in the generator prefix\n self.T = tk.vqvae.context_length # target length (48)\n self.hf = self.pre.tokenizer if hasattr(self.pre, "tokenizer") else None\n def enc(self, text): return self.pre.encode(text)\n def prefix_ids(self, prompt):\n ids = self.enc(prompt)[-self.P:] # keep most recent context\n return ids + [self.ppad] * (self.P - len(ids))\n def target_ids(self, reply):\n ids = self.enc(" " + reply.strip())[: self.T - 1] + [self.EOS]\n return ids + [self.EOS] * (self.T - len(ids))\n\n@torch.no_grad()\ndef target_codes(codec, tgt): # tgt: LongTensor (B,T) -> idx (B, sum levels)\n return codec.tk.vqvae.encode(tgt)\n\ndef build_inputs(codec, prefix, idx, corrupt=0.1):\n net, vq = codec.net, codec.tk.vqvae.vq\n with torch.no_grad():\n idc = idx.clone()\n if corrupt > 0: # robustness: corrupt conditioning codes (as in paper)\n m = torch.rand_like(idc, dtype=torch.float) < corrupt\n idc[m] = torch.randint(0, 4096, (int(m.sum()),), device=idc.device)\n nc = vq.idx_to_ncm_input(idc) # cumulative recon fed to levels 1..K-1\n zp = codec.tk.vqvae.encoder.wte(prefix)[:, :codec.P, :]\n q0 = net.build_l0_query_input(prefix.shape[0])\n return torch.cat([zp, q0, nc], dim=1)\n\ndef loss_fn(codec, prefix, tgt, corrupt=0.1):\n idx = target_codes(codec, tgt)\n x = build_inputs(codec, prefix, idx, corrupt)\n pad_mask = prefix == codec.ppad\n logits = codec.net(x, prefix_pad_mask=None if not hasattr(codec.net, "_prefix_pad_bias") else pad_mask)\n return F.cross_entropy(logits.reshape(-1, logits.shape[-1]).float(), idx.reshape(-1)), logits, idx\n\n@torch.no_grad()\ndef reply(codec, prompts, temperature=0.8, top_k=20, top_p=0.9, dev="cpu"):\n net, tk = codec.net, codec.tk\n pre = torch.tensor([codec.prefix_ids(p) for p in prompts], device=dev)\n g = net.generate(None, pre, temperature=temperature, top_k=top_k, top_p=top_p)\n _, zl = tk.vqvae.vq.idx_to_zhats(g, return_zlevels=True)\n ids = tk.vqvae.decoder(zl[-1]).argmax(-1).tolist()\n out = []\n for r in ids:\n if codec.EOS in r: r = r[: r.index(codec.EOS)]\n out.append(codec.pre.decode([t for t in r if t not in (codec.EOS, codec.PAD)], skip_special_tokens=True).strip())\n return out\n')
11
+ sys.path.insert(0,os.getcwd())
12
+ # Runs inside a Kaggle kernel (CPU ok). Inputs: /kaggle/input/<SRC>/generator_chat.pt ; outputs ncm_fp16w.onnx + report.
13
+ import os, sys, json, types, time
14
+ os.environ["NCM_ROOT"]="/kaggle/working/w/ncm"
15
+ import torch, torch.nn as nn, numpy as np, ncmchat as N
16
+ import torch.nn.functional as _F
17
+ _orig_interp=_F.interpolate
18
+ def _area_mat(L,o):
19
+ m=np.zeros((o,L),dtype=np.float32)
20
+ for i in range(o):
21
+ a=(i*L)//o; b=-((-(i+1)*L)//o); m[i,a:b]=1.0/(b-a)
22
+ return torch.from_numpy(m)
23
+ def _lm(L,o):
24
+ m=np.zeros((o,L),dtype=np.float32)
25
+ for i in range(o):
26
+ 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
27
+ m[i,i0]+=1-w; m[i,i1]+=w
28
+ return torch.from_numpy(m)
29
+ def _interp(x,size=None,scale_factor=None,mode="nearest",**kw):
30
+ if mode=="area" and x.dim()==3 and size is not None:
31
+ o=size[0] if isinstance(size,(tuple,list)) else size
32
+ return torch.matmul(x,_area_mat(x.shape[-1],o).t())
33
+ if mode=="linear" and x.dim()==3 and size is not None and not kw.get("align_corners"):
34
+ o=size[0] if isinstance(size,(tuple,list)) else size
35
+ return torch.matmul(x,_lm(x.shape[-1],o).t())
36
+ return _orig_interp(x,size=size,scale_factor=scale_factor,mode=mode,**kw)
37
+ _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)
38
+ _F.interpolate=_interp
39
+ SRC=os.environ.get("SRC","ncm-chat-smoke")
40
+ 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]
41
+ gp=[c for c in cands if os.path.exists(c)][0]; print("using",gp,flush=True)
42
+ PEFF=int(os.environ.get("PEFF","128"))
43
+ import re
44
+ _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)
45
+ CFG,tk,net=N.load("ck",cfg_path="ck/config_p.yaml",dev="cpu",load_gen=False); net.eval()
46
+ sd=torch.load(gp,map_location="cpu",weights_only=False)["model"]
47
+ own=net.state_dict(); sdf={k:v.float() for k,v in sd.items() if k in own and own[k].shape==v.shape}
48
+ print("skipped keys:",[k for k in sd if k not in sdf],flush=True)
49
+ 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"
50
+ codec=N.Codec(CFG,tk,net); offs=[0]
51
+ for l in net.levels: offs.append(offs[-1]+l)
52
+ class G(nn.Module):
53
+ def __init__(s):
54
+ super().__init__(); s.net=net; s.vq=tk.vqvae.vq; s.dec=tk.vqvae.decoder; s.enc=tk.vqvae.encoder
55
+ def forward(s, prefix, noise, temp):
56
+ st={"i":0}
57
+ def samp(self_, logits, temperature, top_k, top_p):
58
+ i=st["i"]; st["i"]+=1
59
+ nz=noise[:, offs[i]:offs[i+1], :]
60
+ v,_=torch.topk(logits,20,dim=-1)
61
+ lg=torch.where(logits<v[...,-1:],torch.full_like(logits,-1e9),logits)
62
+ return (lg+temp*nz).argmax(-1)
63
+ s.net._sample_level=types.MethodType(samp,s.net)
64
+ g=s.net.generate(None,prefix,temperature=1.0,top_k=20,top_p=0.9)
65
+ _,zl=s.vq.idx_to_zhats(g,return_zlevels=True)
66
+ return s.dec(zl[-1]).argmax(-1)
67
+ m=G().eval()
68
+ tests=["Hi, how are you?","What is the capital of France?","Tell me a joke.","What do you like to do for fun?","Can you recommend a good book?","How do I boil an egg?","I'm feeling sad today.","What is the meaning of life?"]
69
+ prompts=[N.fmt_prompt([("user",t)]) for t in tests]
70
+ pre=torch.tensor([codec.prefix_ids(p) for p in prompts])
71
+ Z=torch.zeros(1,offs[-1],4096); T0=torch.tensor(0.0)
72
+ def txt(ids):
73
+ ids=list(ids)
74
+ if codec.EOS in ids: ids=ids[:ids.index(codec.EOS)]
75
+ return codec.pre.decode([t for t in ids if t not in (codec.EOS,codec.PAD)],skip_special_tokens=True).strip()
76
+ with torch.no_grad(): ref=[m(pre[i:i+1],Z,T0)[0].tolist() for i in range(len(tests))]
77
+ print("torch greedy:",[txt(r) for r in ref],flush=True)
78
+ os.makedirs("/kaggle/working/out",exist_ok=True)
79
+ f32="/kaggle/working/ncm_fp32.onnx"
80
+ with torch.no_grad(): torch.onnx.export(m,(pre[:1],Z,T0),f32,input_names=["prefix","noise","temp"],output_names=["ids"],opset_version=17,dynamo=False)
81
+ print("fp32 onnx MB",os.path.getsize(f32)/1e6,flush=True)
82
+ import onnxruntime as ort
83
+ from onnxruntime.quantization import quantize_dynamic, QuantType
84
+ import onnx
85
+ from onnx import numpy_helper, helper
86
+ mo=onnx.load(f32); g=mo.graph; inits={i.name:i for i in g.initializer}; newn=[]; ng=0
87
+ for n in g.node:
88
+ if n.op_type=="Gemm" and n.input[1] in inits and (len(n.input)<3 or n.input[2] in inits):
89
+ at={a.name:helper.get_attribute_value(a) for a in n.attribute}
90
+ W=numpy_helper.to_array(inits[n.input[1]]); W=(W.T if at.get("transB",0) else W)*at.get("alpha",1.0)
91
+ wn=n.input[1]+"_mm"; g.initializer.append(numpy_helper.from_array(W.astype("float32"),wn))
92
+ mm=n.output[0]+"_mmout" if len(n.input)>=3 else n.output[0]
93
+ newn.append(helper.make_node("MatMul",[n.input[0],wn],[mm],name=n.name+"_mm"))
94
+ if len(n.input)>=3: newn.append(helper.make_node("Add",[mm,n.input[2]],[n.output[0]],name=n.name+"_add"))
95
+ ng+=1
96
+ else: newn.append(n)
97
+ # remove Identity nodes fed by initializers (they hid the weights from the int8 quantizer)
98
+ inits_all={i.name for i in g.initializer}
99
+ alias={}
100
+ final=[]
101
+ for n in newn:
102
+ if n.op_type=="Identity" and (n.input[0] in inits_all or n.input[0] in alias):
103
+ alias[n.output[0]]=n.input[0]
104
+ else: final.append(n)
105
+ def res(x):
106
+ while x in alias: x=alias[x]
107
+ return x
108
+ for n in final:
109
+ for j,x in enumerate(n.input): n.input[j]=res(x)
110
+ for o in g.output:
111
+ if o.name in alias: raise SystemExit("identity feeds graph output")
112
+ del g.node[:]; g.node.extend(final); print("gemm->matmul",ng,"identity removed",len(alias),flush=True)
113
+ f32m="/kaggle/working/ncm_fp32_mm.onnx"; onnx.save(mo,f32m)
114
+ i8="/kaggle/working/out/ncm_fp16w.onnx" # weights stored fp16 + Cast->fp32 at load (compute stays fp32)
115
+ mo=onnx.load(f32m); g=mo.graph; casts=[]; nconv=0
116
+ for init in list(g.initializer):
117
+ if init.data_type==onnx.TensorProto.FLOAT and int(np.prod(init.dims))>=1024:
118
+ arr=numpy_helper.to_array(init); h=numpy_helper.from_array(arr.astype(np.float16),init.name+"_h")
119
+ casts.append(helper.make_node("Cast",[init.name+"_h"],[init.name],to=onnx.TensorProto.FLOAT,name=init.name+"_cast"))
120
+ g.initializer.remove(init); g.initializer.append(h); nconv+=1
121
+ nodes=list(g.node); del g.node[:]; g.node.extend(casts+nodes)
122
+ onnx.save(mo,i8); print("fp16-weights onnx MB",os.path.getsize(i8)/1e6,"converted",nconv,flush=True)
123
+ res={"files_MB":{"fp32":os.path.getsize(f32)/1e6,"fp16w":os.path.getsize(i8)/1e6},"cases":[]}
124
+ for name,path in [("fp32",f32m),("fp16w",i8)]:
125
+ s=ort.InferenceSession(path,providers=["CPUExecutionProvider"]); outs=[]
126
+ t=time.time()
127
+ for i in range(len(tests)):
128
+ o=s.run(None,{"prefix":pre[i:i+1].numpy(),"noise":Z.numpy(),"temp":np.array(0.0,dtype=np.float32)})[0][0].tolist()
129
+ outs.append(o)
130
+ 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]}
131
+ print(name,"exact-match",res[name]["exact_match_vs_torch"],"/",len(tests),"s/reply",round(dt,2),flush=True)
132
+ for t_,o in zip(tests,outs): print(" ",t_,"->",txt(o),flush=True)
133
+ res["torch_replies"]=[txt(r) for r in ref]
134
+ # tokenizer parity vectors for the browser
135
+ res["tok_vectors"]=[{"prompt":p,"ids":codec.enc(p),"prefix_ids":codec.prefix_ids(p)[:len(codec.enc(p))]} for p in prompts[:4]]
136
+ res["EOS"]=codec.EOS; res["PAD"]=codec.PAD; res["ppad"]=codec.ppad; res["P"]=codec.P; res["offs"]=offs
137
+ json.dump(res,open("/kaggle/working/out/report.json","w"),indent=1)
138
+ print("done",flush=True)
code/ncmchat.py ADDED
@@ -0,0 +1,76 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """NCM chat fine-tune: reuse released XSum NCM (frozen multiscale VQ tokenizer + prefix-conditioned generator),
2
+ fine-tune the generator on (conversation prefix -> short reply). Needs HazyResearch/ncm on NCM_ROOT."""
3
+ import os, sys, random, torch, torch.nn.functional as F
4
+ from omegaconf import OmegaConf
5
+ NCM_ROOT = os.environ.get("NCM_ROOT", "/tmp/ncm"); sys.path.insert(0, NCM_ROOT)
6
+ from models.next_concept import NextConceptTransformer
7
+ from tokenization.vqvae_tokenizer import VQVAETokenizer
8
+
9
+ def load(ck, cfg_path=None, dev="cpu", load_gen=True):
10
+ CFG = OmegaConf.load(cfg_path or os.path.join(ck, "config.yaml"))
11
+ CFG.tokenizer.vqvae.checkpoint_path = os.path.join(ck, CFG.checkpoints.tokenizer)
12
+ tk = VQVAETokenizer.from_config(CFG.tokenizer)
13
+ tk.vqvae = tk.vqvae.to(dev).eval()
14
+ for p in tk.vqvae.parameters(): p.requires_grad = False
15
+ net = NextConceptTransformer(vqvae_local=tk.vqvae, config=CFG.model).to(dev)
16
+ if load_gen:
17
+ sd = torch.load(os.path.join(ck, CFG.checkpoints.generator), map_location="cpu", weights_only=False)
18
+ net.load_state_dict(sd["model"], strict=True)
19
+ return CFG, tk, net
20
+
21
+ def fmt_prompt(turns):
22
+ """turns: list of (role, text) ending with a user turn -> prompt string"""
23
+ s = "".join(f"{'User' if r=='user' else 'Assistant'}: {t}\n" for r, t in turns)
24
+ return s + "Assistant:"
25
+
26
+ class Codec:
27
+ def __init__(self, CFG, tk, net):
28
+ self.tk, self.net, self.P = tk, net, int(CFG.model.prefix_len)
29
+ self.pre = tk.pre_tokenizer
30
+ self.EOS, self.PAD = self.pre.eos_token_id, self.pre.pad_token_id
31
+ self.ppad = int(CFG.model.prefix_pad_token_id) # pad id used in the generator prefix
32
+ self.T = tk.vqvae.context_length # target length (48)
33
+ self.hf = self.pre.tokenizer if hasattr(self.pre, "tokenizer") else None
34
+ def enc(self, text): return self.pre.encode(text)
35
+ def prefix_ids(self, prompt):
36
+ ids = self.enc(prompt)[-self.P:] # keep most recent context
37
+ return ids + [self.ppad] * (self.P - len(ids))
38
+ def target_ids(self, reply):
39
+ ids = self.enc(" " + reply.strip())[: self.T - 1] + [self.EOS]
40
+ return ids + [self.EOS] * (self.T - len(ids))
41
+
42
+ @torch.no_grad()
43
+ def target_codes(codec, tgt): # tgt: LongTensor (B,T) -> idx (B, sum levels)
44
+ return codec.tk.vqvae.encode(tgt)
45
+
46
+ def build_inputs(codec, prefix, idx, corrupt=0.1):
47
+ net, vq = codec.net, codec.tk.vqvae.vq
48
+ with torch.no_grad():
49
+ idc = idx.clone()
50
+ if corrupt > 0: # robustness: corrupt conditioning codes (as in paper)
51
+ m = torch.rand_like(idc, dtype=torch.float) < corrupt
52
+ idc[m] = torch.randint(0, 4096, (int(m.sum()),), device=idc.device)
53
+ nc = vq.idx_to_ncm_input(idc) # cumulative recon fed to levels 1..K-1
54
+ zp = codec.tk.vqvae.encoder.wte(prefix)[:, :codec.P, :]
55
+ q0 = net.build_l0_query_input(prefix.shape[0])
56
+ return torch.cat([zp, q0, nc], dim=1)
57
+
58
+ def loss_fn(codec, prefix, tgt, corrupt=0.1):
59
+ idx = target_codes(codec, tgt)
60
+ x = build_inputs(codec, prefix, idx, corrupt)
61
+ pad_mask = prefix == codec.ppad
62
+ logits = codec.net(x, prefix_pad_mask=None if not hasattr(codec.net, "_prefix_pad_bias") else pad_mask)
63
+ return F.cross_entropy(logits.reshape(-1, logits.shape[-1]).float(), idx.reshape(-1)), logits, idx
64
+
65
+ @torch.no_grad()
66
+ def reply(codec, prompts, temperature=0.8, top_k=20, top_p=0.9, dev="cpu"):
67
+ net, tk = codec.net, codec.tk
68
+ pre = torch.tensor([codec.prefix_ids(p) for p in prompts], device=dev)
69
+ g = net.generate(None, pre, temperature=temperature, top_k=top_k, top_p=top_p)
70
+ _, zl = tk.vqvae.vq.idx_to_zhats(g, return_zlevels=True)
71
+ ids = tk.vqvae.decoder(zl[-1]).argmax(-1).tolist()
72
+ out = []
73
+ for r in ids:
74
+ if codec.EOS in r: r = r[: r.index(codec.EOS)]
75
+ out.append(codec.pre.decode([t for t in r if t not in (codec.EOS, codec.PAD)], skip_special_tokens=True).strip())
76
+ return out
generator_chat.pt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:0b21acb483deb0814dbaee271f054fd7ebb43019b18321dd28d2de5dd75a87d6
3
+ size 177107827
training_report.json ADDED
@@ -0,0 +1,91 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "src": {
3
+ "everyday": 8625,
4
+ "dolly": 10544,
5
+ "alpaca": 31323
6
+ },
7
+ "n_train": 21084,
8
+ "steps": 4000,
9
+ "samples": [
10
+ {
11
+ "temp": 0.7,
12
+ "user": "Hi, how are you?",
13
+ "ncm_reply": "Hello! How can I help you today?"
14
+ },
15
+ {
16
+ "temp": 0.7,
17
+ "user": "What is the capital of France?",
18
+ "ncm_reply": "The capital capital of Paris is Paris."
19
+ },
20
+ {
21
+ "temp": 0.7,
22
+ "user": "Tell me a joke.",
23
+ "ncm_reply": "Hello! a weather??"
24
+ },
25
+ {
26
+ "temp": 0.7,
27
+ "user": "What do you like to do for fun?",
28
+ "ncm_reply": "Enough to make making fun about wishing your fun, and love books and making you look fun!"
29
+ },
30
+ {
31
+ "temp": 0.7,
32
+ "user": "Can you recommend a good book?",
33
+ "ncm_reply": "A book is a good book."
34
+ },
35
+ {
36
+ "temp": 0.7,
37
+ "user": "How do I boil an egg?",
38
+ "ncm_reply": "When you boil an egg, you might try."
39
+ },
40
+ {
41
+ "temp": 0.7,
42
+ "user": "I'm feeling sad today.",
43
+ "ncm_reply": "I am very sorry."
44
+ },
45
+ {
46
+ "temp": 0.7,
47
+ "user": "What is the meaning of life?",
48
+ "ncm_reply": "The meaning meaning of life means that."
49
+ },
50
+ {
51
+ "temp": 0.0,
52
+ "user": "Hi, how are you?",
53
+ "ncm_reply": "Hello! How can I help you today?"
54
+ },
55
+ {
56
+ "temp": 0.0,
57
+ "user": "What is the capital of France?",
58
+ "ncm_reply": "Paris is a capital capital."
59
+ },
60
+ {
61
+ "temp": 0.0,
62
+ "user": "Tell me a joke.",
63
+ "ncm_reply": "you don't help you help you today?"
64
+ },
65
+ {
66
+ "temp": 0.0,
67
+ "user": "What do you like to do for fun?",
68
+ "ncm_reply": "What fun items do you like like fun, cake, butter, butter, or.."
69
+ },
70
+ {
71
+ "temp": 0.0,
72
+ "user": "Can you recommend a good book?",
73
+ "ncm_reply": "Stars is a good book."
74
+ },
75
+ {
76
+ "temp": 0.0,
77
+ "user": "How do I boil an egg?",
78
+ "ncm_reply": "You can tap a fruit cake egg."
79
+ },
80
+ {
81
+ "temp": 0.0,
82
+ "user": "I'm feeling sad today.",
83
+ "ncm_reply": "I'm feeling sorry.."
84
+ },
85
+ {
86
+ "temp": 0.0,
87
+ "user": "What is the meaning of life?",
88
+ "ncm_reply": "Life is a daily meaning of life."
89
+ }
90
+ ]
91
+ }