Download experiments/add_class.py from DJLougen/pixelgen2: direct link, hf CLI and curl.
- Browser
- Download file 5.97 kB
-
https://huggingface.co/DJLougen/pixelgen2/resolve/main/experiments/add_class.py
- Command line
-
hf download hf://DJLougen/pixelgen2/experiments/add_class.py
-
curl -L -o add_class.py https://huggingface.co/DJLougen/pixelgen2/resolve/main/experiments/add_class.py
5.97 kB
| """Measure exactly how PixelGen changed after adding one class. | |
| Compares the frozen before-state (experiments/before.pt, before_outputs.npz, | |
| before_meta.json) against the live model built from current source. | |
| Reports: | |
| 1. tensor shapes before -> after | |
| 2. raw parameter diff (how many values differ by index) | |
| 3. semantic diff (how many values differ once rows/cols are aligned by | |
| class name / word, i.e. the change that actually matters) | |
| 4. vocab index remap (sorted vocab means new words shift old columns) | |
| 5. output equivalence on every pre-existing prompt (bit-exact or not) | |
| 6. what the new prompts do now vs before | |
| """ | |
| import json | |
| from pathlib import Path | |
| import numpy as np | |
| import torch | |
| from pixelgen.model import PixelGen | |
| HERE = Path(__file__).resolve().parent | |
| before_sd = torch.load(HERE / "before.pt", map_location="cpu", weights_only=True) | |
| before_out = np.load(HERE / "before_outputs.npz") | |
| meta = json.load(open(HERE / "before_meta.json")) | |
| b_vocab, b_classes, prompts = meta["vocab"], meta["classes"], meta["prompts"] | |
| m = PixelGen() | |
| after_sd = m.state_dict() | |
| a_vocab, a_classes = m.vocab, m.classes | |
| new_classes = [c for c in a_classes if c not in b_classes] | |
| new_words = [w for w in a_vocab if w not in b_vocab] | |
| print("=" * 70) | |
| print(f"new classes: {new_classes} new words: {new_words}") | |
| print(f"vocab {len(b_vocab)} -> {len(a_vocab)} classes {len(b_classes)} -> {len(a_classes)}") | |
| # 1. shapes ------------------------------------------------------------- | |
| print("\n[1] tensor shapes") | |
| tot_b = tot_a = 0 | |
| for k in after_sd: | |
| sb, sa = tuple(before_sd[k].shape), tuple(after_sd[k].shape) | |
| tot_b += before_sd[k].numel(); tot_a += after_sd[k].numel() | |
| flag = " <-- grew" if sb != sa else "" | |
| print(f" {k:24} {str(sb):>16} -> {str(sa):<16}{flag}") | |
| print(f" total values {tot_b} -> {tot_a} (+{tot_a - tot_b})") | |
| # 2. raw diff ------------------------------------------------------------ | |
| print("\n[2] raw diff by index (naive view)") | |
| for k in after_sd: | |
| b, a = before_sd[k], after_sd[k] | |
| if b.shape == a.shape: | |
| n = int((b != a).sum()) | |
| print(f" {k:24} {n:5d} / {a.numel()} values differ") | |
| else: | |
| # compare overlapping prefix region | |
| sl = tuple(slice(0, s) for s in b.shape) | |
| n = int((b != a[sl]).sum()) | |
| print(f" {k:24} {n:5d} / {b.numel()} values differ in the old-shaped prefix (+{a.numel()-b.numel()} new)") | |
| # 3. semantic diff (align by names) --------------------------------------- | |
| print("\n[3] semantic diff (rows/cols aligned by class name / word)") | |
| def aligned_linear(name): | |
| b, a = before_sd[f"{name}.weight"], after_sd[f"{name}.weight"] | |
| rows = b_classes if name == "word_to_class" else m.ramps | |
| changed = [] | |
| for ri, r in enumerate(rows): | |
| for w in b_vocab: | |
| bv = b[ri, b_vocab[w]].item() | |
| av = a[a_classes.index(r) if name == "word_to_class" else ri, a_vocab[w]].item() | |
| if bv != av: | |
| changed.append((r, w, bv, av)) | |
| return changed | |
| for name in ("word_to_class", "word_to_ramp"): | |
| ch = aligned_linear(name) | |
| print(f" {name:16} old (row,word) entries changed: {len(ch)} {ch[:5]}") | |
| bb, ab = before_sd["word_to_class.bias"], after_sd["word_to_class.bias"] | |
| print(" word_to_class.bias old-class entries changed:", | |
| sum(bb[i].item() != ab[a_classes.index(c)].item() for i, c in enumerate(b_classes))) | |
| for bank in ("on_bank", "free_bank"): | |
| b, a = before_sd[bank], after_sd[bank] | |
| n = sum(int((b[i] != a[a_classes.index(c)]).sum()) for i, c in enumerate(b_classes)) | |
| print(f" {bank:16} old-class template cells changed: {n}") | |
| for k in ("shade_bank", "ramp_table", "outline.weight", "palette_conv.weight"): | |
| print(f" {k:16} identical: {torch.equal(before_sd[k], after_sd[k])}") | |
| # 4. vocab remap ---------------------------------------------------------- | |
| print("\n[4] vocab index remap caused by sorted insertion") | |
| moved = [(w, b_vocab[w], a_vocab[w]) for w in b_vocab if b_vocab[w] != a_vocab[w]] | |
| print(f" {len(moved)} of {len(b_vocab)} old words changed column index") | |
| print(" first few:", moved[:6]) | |
| print(" new word columns:", {w: a_vocab[w] for w in new_words}) | |
| # 5. output equivalence --------------------------------------------------- | |
| print("\n[5] output equivalence on pre-existing prompts (3 seeds each)") | |
| same = diff = 0 | |
| changed_prompts = [] | |
| for i, p in enumerate(prompts): | |
| b = before_out[f"p{i}"] | |
| a = m.generate([p] * 3, [0, 1, 2], scale=1) | |
| if np.array_equal(a, b): | |
| same += 1 | |
| else: | |
| diff += 1 | |
| changed_prompts.append(p) | |
| print(f" identical: {same} changed: {diff} -> {changed_prompts}") | |
| # 6. new prompt behaviour --------------------------------------------------- | |
| print("\n[6] what the new-class prompts resolve to now") | |
| ref = {c: (m.generate([c], [0], scale=1)[0].sum(-1) > 0) for c in a_classes} | |
| for p in ["mushroom", "red mushroom", "purple fungus", "toadstool", "dragon", ""]: | |
| mask = m.generate([p], [0], scale=1)[0].sum(-1) > 0 | |
| cls = [c for c, r in ref.items() if np.array_equal(r, mask)] | |
| i = prompts.index(p) if p in prompts else None | |
| was = "n/a" | |
| if i is not None: | |
| bm = before_out[f"p{i}"][0].sum(-1) > 0 | |
| was = [c for c, r in ref.items() if np.array_equal(r, bm)] | |
| print(f" {p!r:18} before -> {was} after -> {cls}") | |
| # image --------------------------------------------------------------------- | |
| from PIL import Image | |
| S = 6; cell = 16 * S; pad = 4 | |
| rows = [("before", b_classes), ("after", a_classes)] | |
| W = max(len(c) for _, c in rows) * (cell + pad) + pad | |
| img = Image.new("RGB", (W, 2 * (cell + pad) + pad), (29, 43, 83)) | |
| for r, (_, classes) in enumerate(rows): | |
| for c, cls in enumerate(classes): | |
| img.paste(Image.fromarray(m.generate([cls], [0], scale=S)[0]), (pad + c * (cell + pad), pad + r * (cell + pad))) | |
| img.save(HERE / "before_after.png") | |
| print(f"\nwrote {HERE/'before_after.png'} (top: old classes rendered by new model; bottom: all classes)") | |