Tarul commited on
Commit
a6415b7
·
verified ·
1 Parent(s): f21a310

Upload pxg_tiny/pipeline.py with huggingface_hub

Browse files
Files changed (1) hide show
  1. pxg_tiny/pipeline.py +129 -0
pxg_tiny/pipeline.py ADDED
@@ -0,0 +1,129 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """PXG-Tiny high-level pipeline (offline, NumPy-only).
2
+
3
+ PXGPipeline wraps OfflinePipeline with:
4
+ * generate_pixels(text) -> uint8 grid (16x16 palette indices)
5
+ * generate_png(text, path) -> scaled RGBA PNG
6
+ * generate_turnaround(text, seeds) / variations
7
+ * should_ask / ask -> rule-based clarify/refuse gate (ask-first)
8
+ * auto-retry sampling scored by quality.check_sprite (reject-and-log)
9
+ """
10
+ import sys
11
+ from collections import Counter
12
+ from pathlib import Path
13
+
14
+ import numpy as np
15
+
16
+ sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
17
+ from pxg_tiny.runtime_pipeline import OfflinePipeline # noqa: E402
18
+ from pxg_tiny import quality as Q # noqa: E402
19
+ from pxg_tiny.render import save_png # noqa: E402
20
+
21
+ DEFAULT_BUNDLE = Path(__file__).resolve().parents[2] / "weights"
22
+
23
+
24
+ class PXGPipeline:
25
+ def __init__(self, bundle_dir=None):
26
+ self.pipe = OfflinePipeline(bundle_dir or DEFAULT_BUNDLE)
27
+
28
+ # ------------------------------------------------------------- gate --
29
+ @staticmethod
30
+ def should_ask(text):
31
+ return Q.should_ask(text)
32
+
33
+ @classmethod
34
+ def ask(cls, text):
35
+ """Return a clarifying question / refusal message, or None if the
36
+ prompt is acceptable."""
37
+ label, msg = cls.should_ask(text)
38
+ return msg if label in ("clarify", "refuse") else None
39
+
40
+ # -------------------------------------------------------- sampling --
41
+ @staticmethod
42
+ def _anchor_ids(spec):
43
+ """Canonical corpus-style caption ids for the anchor retry stage —
44
+ prompt normalization using only our own parser (offline)."""
45
+ from pxg_tiny.config import encode_caption
46
+ cls = spec.get("cls")
47
+ cap = Q.ANCHOR_CAPTIONS.get(cls)
48
+ if cap is None:
49
+ return None
50
+ if (spec.get("material") and cls not in ("wizard", "archer", "zombie")
51
+ and f" {spec['material']}" not in cap):
52
+ art = "an" if spec["material"][0] in "aeiou" else "a"
53
+ cap = cap.replace("a ", f"{art} {spec['material']} ", 1)
54
+ return np.array(encode_caption(cap), dtype=np.int64)
55
+
56
+ def generate_pixels(self, text, seed=0, temperature=None, top_k=None,
57
+ retries=8, enforce_quality=True):
58
+ """English instruction -> 16x16 grid of palette indices.
59
+
60
+ Escalating verify-and-retry schedule (all offline, self-contained):
61
+ attempt 0 raw sample
62
+ attempts 1-3 self-guidance: palette-logit bias, strength ramps
63
+ attempts 4-5 + spatial bias (face box / vial margins)
64
+ attempts 6-7 + canonical anchor caption (prompt normalization)
65
+ The first sprite passing the grounding checks wins; reject reasons
66
+ are counted and returned in the metadata dict."""
67
+ label, msg = self.should_ask(text)
68
+ if label != "accept":
69
+ return None, {"gate": label, "message": msg}
70
+
71
+ spec = Q.parse_prompt(text)
72
+ anchor_ids = self._anchor_ids(spec)
73
+ struct_prefix = Q.STRUCTURAL_PREFIX.get(spec.get("cls"))
74
+ rejects = Counter()
75
+ grid = None
76
+ for k in range(max(1, retries)):
77
+ lb = None
78
+ ids = None
79
+ ptoks = struct_prefix if (struct_prefix is not None and k >= 3) else None
80
+ if k >= 1:
81
+ lb = Q.bias_from_spec(spec, strength=1.6 + 0.5 * min(k, 5))
82
+ if k >= 4:
83
+ combined = np.zeros((256, 32), dtype=np.float64)
84
+ if lb is not None:
85
+ combined[:] = np.asarray(lb, dtype=np.float64)[None, :]
86
+ combined += Q.positional_bias_from_spec(
87
+ spec, strength=1.4 + 0.3 * (k - 4))
88
+ lb = combined
89
+ if k >= 6 and anchor_ids is not None:
90
+ ids = anchor_ids # canonical anchor
91
+ grid = self.pipe.generate_grid(text, seed=seed + 1013 * k,
92
+ temperature=temperature,
93
+ top_k=top_k, logit_bias=lb,
94
+ ids_override=ids,
95
+ prefix_tokens=ptoks)
96
+ if enforce_quality:
97
+ ok, reasons = Q.check_sprite(grid, spec)
98
+ if ok:
99
+ return grid, {"gate": "accept", "seed": seed + 1013 * k,
100
+ "attempt": k, "rejects": dict(rejects)}
101
+ for r in reasons:
102
+ rejects[r] += 1
103
+ else:
104
+ return grid, {"gate": "accept", "seed": seed, "attempt": k,
105
+ "rejects": {}}
106
+ # fall back to the last sample rather than failing hard
107
+ return grid, {"gate": "accept_degraded", "seed": seed,
108
+ "attempt": retries, "rejects": dict(rejects)}
109
+
110
+ def generate_png(self, text, path, scale=8, seed=0, **kw):
111
+ grid, meta = self.generate_pixels(text, seed=seed, **kw)
112
+ if grid is None:
113
+ return None, meta
114
+ save_png(grid, Path(path), scale=scale)
115
+ meta["path"] = str(path)
116
+ return grid, meta
117
+
118
+ def generate_turnaround(self, text, seeds=(1, 2, 3, 4), **kw):
119
+ """Several consistent samples of the same instruction (seed family);
120
+ tiny-model analog of the big sibling's multiview turnaround."""
121
+ grids = []
122
+ for s in seeds:
123
+ g, m = self.generate_pixels(text, seed=s, **kw)
124
+ grids.append((g, m))
125
+ return grids
126
+
127
+ def variations(self, text, k=4, start_seed=0, **kw):
128
+ return self.generate_turnaround(text, seeds=tuple(
129
+ start_seed + 37 * i for i in range(k)), **kw)