Download code/ncmchat.py from danielharkin21/ncm-chat-experimental-generator: direct link, hf CLI and curl.
- Browser
- Download file 4.07 kB
-
https://huggingface.co/danielharkin21/ncm-chat-experimental-generator/resolve/main/code/ncmchat.py
- Command line
-
hf download hf://danielharkin21/ncm-chat-experimental-generator/code/ncmchat.py
-
curl -L -o ncmchat.py https://huggingface.co/danielharkin21/ncm-chat-experimental-generator/resolve/main/code/ncmchat.py
4.07 kB
| """NCM chat fine-tune: reuse released XSum NCM (frozen multiscale VQ tokenizer + prefix-conditioned generator), | |
| fine-tune the generator on (conversation prefix -> short reply). Needs HazyResearch/ncm on NCM_ROOT.""" | |
| import os, sys, random, torch, torch.nn.functional as F | |
| from omegaconf import OmegaConf | |
| NCM_ROOT = os.environ.get("NCM_ROOT", "/tmp/ncm"); sys.path.insert(0, NCM_ROOT) | |
| from models.next_concept import NextConceptTransformer | |
| from tokenization.vqvae_tokenizer import VQVAETokenizer | |
| def load(ck, cfg_path=None, dev="cpu", load_gen=True): | |
| CFG = OmegaConf.load(cfg_path or os.path.join(ck, "config.yaml")) | |
| CFG.tokenizer.vqvae.checkpoint_path = os.path.join(ck, CFG.checkpoints.tokenizer) | |
| tk = VQVAETokenizer.from_config(CFG.tokenizer) | |
| tk.vqvae = tk.vqvae.to(dev).eval() | |
| for p in tk.vqvae.parameters(): p.requires_grad = False | |
| net = NextConceptTransformer(vqvae_local=tk.vqvae, config=CFG.model).to(dev) | |
| if load_gen: | |
| sd = torch.load(os.path.join(ck, CFG.checkpoints.generator), map_location="cpu", weights_only=False) | |
| net.load_state_dict(sd["model"], strict=True) | |
| return CFG, tk, net | |
| def fmt_prompt(turns): | |
| """turns: list of (role, text) ending with a user turn -> prompt string""" | |
| s = "".join(f"{'User' if r=='user' else 'Assistant'}: {t}\n" for r, t in turns) | |
| return s + "Assistant:" | |
| class Codec: | |
| def __init__(self, CFG, tk, net): | |
| self.tk, self.net, self.P = tk, net, int(CFG.model.prefix_len) | |
| self.pre = tk.pre_tokenizer | |
| self.EOS, self.PAD = self.pre.eos_token_id, self.pre.pad_token_id | |
| self.ppad = int(CFG.model.prefix_pad_token_id) # pad id used in the generator prefix | |
| self.T = tk.vqvae.context_length # target length (48) | |
| self.hf = self.pre.tokenizer if hasattr(self.pre, "tokenizer") else None | |
| def enc(self, text): return self.pre.encode(text) | |
| def prefix_ids(self, prompt): | |
| ids = self.enc(prompt)[-self.P:] # keep most recent context | |
| return ids + [self.ppad] * (self.P - len(ids)) | |
| def target_ids(self, reply): | |
| ids = self.enc(" " + reply.strip())[: self.T - 1] + [self.EOS] | |
| return ids + [self.EOS] * (self.T - len(ids)) | |
| def target_codes(codec, tgt): # tgt: LongTensor (B,T) -> idx (B, sum levels) | |
| return codec.tk.vqvae.encode(tgt) | |
| def build_inputs(codec, prefix, idx, corrupt=0.1): | |
| net, vq = codec.net, codec.tk.vqvae.vq | |
| with torch.no_grad(): | |
| idc = idx.clone() | |
| if corrupt > 0: # robustness: corrupt conditioning codes (as in paper) | |
| m = torch.rand_like(idc, dtype=torch.float) < corrupt | |
| idc[m] = torch.randint(0, 4096, (int(m.sum()),), device=idc.device) | |
| nc = vq.idx_to_ncm_input(idc) # cumulative recon fed to levels 1..K-1 | |
| zp = codec.tk.vqvae.encoder.wte(prefix)[:, :codec.P, :] | |
| q0 = net.build_l0_query_input(prefix.shape[0]) | |
| return torch.cat([zp, q0, nc], dim=1) | |
| def loss_fn(codec, prefix, tgt, corrupt=0.1): | |
| idx = target_codes(codec, tgt) | |
| x = build_inputs(codec, prefix, idx, corrupt) | |
| pad_mask = prefix == codec.ppad | |
| logits = codec.net(x, prefix_pad_mask=None if not hasattr(codec.net, "_prefix_pad_bias") else pad_mask) | |
| return F.cross_entropy(logits.reshape(-1, logits.shape[-1]).float(), idx.reshape(-1)), logits, idx | |
| def reply(codec, prompts, temperature=0.8, top_k=20, top_p=0.9, dev="cpu"): | |
| net, tk = codec.net, codec.tk | |
| pre = torch.tensor([codec.prefix_ids(p) for p in prompts], device=dev) | |
| g = net.generate(None, pre, temperature=temperature, top_k=top_k, top_p=top_p) | |
| _, zl = tk.vqvae.vq.idx_to_zhats(g, return_zlevels=True) | |
| ids = tk.vqvae.decoder(zl[-1]).argmax(-1).tolist() | |
| out = [] | |
| for r in ids: | |
| if codec.EOS in r: r = r[: r.index(codec.EOS)] | |
| out.append(codec.pre.decode([t for t in r if t not in (codec.EOS, codec.PAD)], skip_special_tokens=True).strip()) | |
| return out | |