Spaces:
Running
Running
| """Recipe-finder v2 for StarCoder2 MLP compression. | |
| Improvements over v1: (1) sequence PACKING + batching for ~8x token throughput, | |
| (2) FEATURE distillation -- match each compressed MLP's output to what the | |
| ORIGINAL MLP produces on the same input (the "mimic the weights" / doc-to-LoRA | |
| signal), computed by recomputing the frozen orig MLP on the student's own | |
| activations (cheap, no full second forward), (3) optional logit KL with | |
| temperature, (4) tuned layernorms, (5) expandable_segments to fit bigger banks. | |
| Objectives: loss = NTP + feat_w * mean_layer relMSE(bank_out, orig_mlp(in)) [+ kl]. | |
| Usage: python train_compress2.py --tag r_feat --E 2048 --feat-w 1 --tune-norms 1 \ | |
| --batch 2 --ctx 512 --steps 4000 | |
| """ | |
| import argparse, itertools, math, time | |
| import torch, torch.nn as nn, torch.nn.functional as F | |
| from transformers import AutoModelForCausalLM, AutoTokenizer | |
| from datasets import load_dataset | |
| DEV = 0 | |
| class Bank(nn.Module): | |
| def __init__(self, src_mlp, E, init): | |
| super().__init__() | |
| if init == "topnorm": | |
| idx = src_mlp.c_proj.weight.data.float().norm(dim=0).topk(E).indices | |
| else: | |
| idx = torch.randperm(src_mlp.c_fc.weight.shape[0])[:E] | |
| self.down = nn.Parameter(src_mlp.c_fc.weight.data[idx].clone().float()) | |
| self.up = nn.Parameter(src_mlp.c_proj.weight.data[:, idx].t().clone().float()) | |
| self.b = nn.Parameter(src_mlp.c_fc.bias.data[idx].clone().float() | |
| if src_mlp.c_fc.bias is not None else torch.zeros(E)) | |
| self.obias = nn.Parameter(src_mlp.c_proj.bias.data.clone().float() | |
| if src_mlp.c_proj.bias is not None | |
| else torch.zeros(src_mlp.c_proj.weight.shape[0])) | |
| self.last_in = None; self.last_out = None | |
| def forward(self, x): | |
| self.last_in = x | |
| act = F.gelu(x.float() @ self.down.t() + self.b, approximate="tanh") | |
| out = (act @ self.up + self.obias).to(x.dtype) | |
| self.last_out = out | |
| return out | |
| def token_stream(tok, ctx, n_docs=8000): | |
| texts = [] | |
| for name, cfg, field in [("codeparrot/codeparrot-clean", None, "content"), | |
| ("HuggingFaceFW/fineweb-edu", "sample-10BT", "text")]: | |
| try: | |
| ds = (load_dataset(name, cfg, split="train", streaming=True) if cfg | |
| else load_dataset(name, split="train", streaming=True)) | |
| for ex in itertools.islice(ds, n_docs): | |
| t = ex.get(field) or "" | |
| if len(t) > 60: texts.append(t) | |
| except Exception as e: | |
| print("FAIL", name, str(e)[:100], flush=True) | |
| import random; random.shuffle(texts) | |
| buf = [] | |
| while True: | |
| for t in texts: | |
| buf.extend(tok(t).input_ids + [tok.eos_token_id or 0]) | |
| while len(buf) >= ctx: | |
| yield torch.tensor(buf[:ctx]); buf = buf[ctx:] | |
| def main(): | |
| ap = argparse.ArgumentParser() | |
| ap.add_argument("--tag", default="run") | |
| ap.add_argument("--E", type=int, default=2048) | |
| ap.add_argument("--init", default="random") | |
| ap.add_argument("--feat-w", type=float, default=1.0) | |
| ap.add_argument("--kl-w", type=float, default=0.0) | |
| ap.add_argument("--kl-temp", type=float, default=2.0) | |
| ap.add_argument("--tune-norms", type=int, default=1) | |
| ap.add_argument("--batch", type=int, default=2) | |
| ap.add_argument("--ctx", type=int, default=512) | |
| ap.add_argument("--steps", type=int, default=4000) | |
| ap.add_argument("--lr", type=float, default=5e-4) | |
| ap.add_argument("--warmup", type=int, default=150) | |
| args = ap.parse_args() | |
| print(f"=== {args.tag}: E={args.E} feat_w={args.feat_w} kl_w={args.kl_w} " | |
| f"norms={args.tune_norms} batch={args.batch} ctx={args.ctx} ===", flush=True) | |
| tok = AutoTokenizer.from_pretrained("bigcode/starcoder2-3b") | |
| m = AutoModelForCausalLM.from_pretrained("bigcode/starcoder2-3b", dtype=torch.bfloat16, | |
| device_map={"": DEV}) | |
| m.config.use_cache = False | |
| for p in m.parameters(): p.requires_grad_(False) | |
| layers = m.model.layers | |
| orig_mlps = [l.mlp for l in layers] | |
| banks = [Bank(l.mlp, args.E, args.init).to(DEV) for l in layers] | |
| def use_banks(on): | |
| for l, om, bk in zip(layers, orig_mlps, banks): | |
| l.mlp = bk if on else om | |
| gen = token_stream(tok, args.ctx) | |
| def get_batch(): | |
| return torch.stack([next(gen) for _ in range(args.batch)]).to(DEV) | |
| def ppl(on, n=15): | |
| m.eval(); use_banks(on); tot = 0.0 | |
| for _ in range(n): | |
| ids = get_batch(); tot += m(ids, labels=ids).loss.item() | |
| return math.exp(tot / n) | |
| op = ppl(False); ip = ppl(True) | |
| print(f"[ref] ORIGINAL {op:.2f} | INIT {ip:.1f} ppl", flush=True) | |
| params = [p for bk in banks for p in bk.parameters()] | |
| if args.tune_norms: | |
| for l in layers: | |
| for mod in (l.input_layernorm, l.post_attention_layernorm): | |
| for p in mod.parameters(): p.requires_grad_(True); params.append(p) | |
| for p in params: p.requires_grad_(True) | |
| m.gradient_checkpointing_enable() | |
| opt = torch.optim.AdamW(params, lr=args.lr, betas=(0.9, 0.95)) | |
| sched = torch.optim.lr_scheduler.LambdaLR(opt, lambda s: | |
| s / args.warmup if s < args.warmup else | |
| 0.5 * (1 + math.cos(math.pi * min(1.0, (s - args.warmup) / max(1, args.steps - args.warmup))))) | |
| t0 = time.time(); ema = None | |
| for step in range(1, args.steps + 1): | |
| ids = get_batch() | |
| if args.kl_w > 0: | |
| use_banks(False); m.eval() | |
| with torch.no_grad(): t_logits = m(ids).logits | |
| use_banks(True); m.train() | |
| out = m(ids, labels=ids) | |
| loss = out.loss; ce = out.loss.item() | |
| if args.feat_w > 0: # mimic each MLP's output | |
| fl = 0.0 | |
| for om, bk in zip(orig_mlps, banks): | |
| with torch.no_grad(): tgt = om(bk.last_in) | |
| fl = fl + ((bk.last_out - tgt).float().pow(2).mean() | |
| / tgt.float().pow(2).mean().clamp_min(1e-6)) | |
| loss = loss + args.feat_w * fl / len(banks) | |
| if args.kl_w > 0: | |
| T = args.kl_temp | |
| kl = F.kl_div(F.log_softmax(out.logits / T, -1), F.softmax(t_logits / T, -1), | |
| reduction="batchmean") * (T * T) / out.logits.shape[1] | |
| loss = loss + args.kl_w * kl | |
| opt.zero_grad(set_to_none=True); loss.backward() | |
| torch.nn.utils.clip_grad_norm_(params, 1.0); opt.step(); sched.step() | |
| ema = ce if ema is None else 0.98 * ema + 0.02 * ce | |
| if step % 25 == 0: | |
| tps = step * args.batch * args.ctx / (time.time() - t0) | |
| print(f"[{args.tag}] step {step}/{args.steps} ce {ce:.3f} ema {ema:.3f} " | |
| f"ppl {math.exp(ema):.1f} (orig {op:.1f}) {tps/1000:.0f}k tok/s", flush=True) | |
| fp = ppl(True, 30) | |
| print(f"\n[result {args.tag}] ORIGINAL {op:.2f} | INIT {ip:.1f} | FINAL {fp:.1f} ppl " | |
| f"(tokens {args.steps*args.batch*args.ctx/1e6:.1f}M)", flush=True) | |
| torch.save({"E": args.E, "init": args.init, "final_ppl": fp, "orig_ppl": op, | |
| "banks": [bk.state_dict() for bk in banks]}, f"/tmp/banks_{args.tag}.pt") | |
| print("DONE " + args.tag, flush=True) | |
| if __name__ == "__main__": | |
| main() | |