Text-to-Image
English
numpy
machine-learning
deep-learning
generative-ai
text-2-image
image-generation
open-weights
model-weights
ai-art
pixel-art
game-development
gamedev
game-assets
asset-generator
sprite-generator
offline
tiny-model
numpy-runtime
int8-quantization
self-supervised
procedural-data
gpt
english-prompts
awesome-ai
File size: 4,887 Bytes
96b8dd1 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 | """PXG-Tiny shared configuration: master palette, tokenizer, classes, model dims.
Single source of truth imported by both the training pipeline and the
inference runtime. All sprites are represented as 16x16 grids of *master
palette indices* (0 = fully transparent alpha, 1..31 = opaque colors).
This makes reconstruction byte-exact by construction and keeps the
generation vocabulary tiny (32 visual symbols).
"""
IMG = 16 # sprite resolution (16x16, even, inside the 8..256 schema band)
N_COLORS = 32 # visual vocabulary size: index 0 = transparent, 1..31 colors
# ---------------------------------------------------------------------------
# Master palette (index -> RGBA). Hand-crafted to span the classic
# warm_fantasy / nes / cool_sci_fi flavor families while staying <= 31 opaque
# colors so a single byte encodes any pixel.
# ---------------------------------------------------------------------------
PALETTE = {
1: (248, 248, 248), # white / specular
2: (217, 221, 230), # pale gray
3: (183, 191, 204), # silver
4: (120, 129, 142), # gray
5: (69, 76, 87), # charcoal
6: (23, 23, 31), # ink (outline black)
7: (212, 61, 51), # red
8: (140, 35, 32), # maroon
9: (232, 137, 46), # orange / terracotta
10: (242, 193, 46), # gold
11: (255, 232, 107), # lemon (gold highlight)
12: (127, 201, 79), # lime
13: (76, 154, 68), # green mid
14: (44, 106, 53), # forest dark
15: (55, 179, 171), # teal
16: (159, 223, 240), # ice / cyan highlight
17: (79, 147, 220), # sky blue
18: (45, 71, 127), # navy deep
19: (124, 80, 173), # purple
20: (180, 140, 224), # light violet
21: (168, 123, 35), # bronze (dark gold)
22: (242, 203, 160), # peach
23: (221, 182, 113), # sand
24: (244, 230, 192), # cream ivory
25: (165, 116, 74), # tan wood
26: (122, 78, 42), # brown mid
27: (76, 48, 26), # dark wood
28: (111, 117, 57), # moss olive
29: (185, 230, 197), # mint pale (slime)
30: (93, 114, 136), # steel blue-gray
31: (238, 173, 174), # blush soft pink
}
# ---------------------------------------------------------------------------
# Text tokenizer (char-level with byte-fallback buckets).
# id 0 = <pad>, 1 = <bos>, 2 = <sep>, 3..6 punctuation, 7..32 a-z,
# 33..62 byte-fallback buckets (ord % 30 + 33), 63 spare.
# Deterministic, lossless round-trip on the supported charset; any other
# Unicode character folds into its byte-fallback bucket (documented lossy
# fold for exotic input, mirrors the "byte fallback" philosophy of the big
# sibling project).
# ---------------------------------------------------------------------------
CHARSET = "abcdefghijklmnopqrstuvwxyz ,:-:'"
PAD_ID, BOS_ID, SEP_ID = 0, 1, 2
_TEXT_VOCAB = 64 # total text-side embedding width
CAP_LEN = 32 # fixed padded caption slot count (incl. <pad>)
PREFIX_LEN = 8 # intent-encoder output prefix vectors fed to decoder
def encode_char(c: str) -> int:
if c == " ": return 3
if c == ",": return 4
if c == "-": return 5
if c == ":": return 6
if "a" <= c <= "z":
return 7 + ord(c) - ord("a")
if c == "'": return 39
# byte fallback bucket for anything else (digits, unicode, etc.)
fb = c.encode("utf-8", "replace")[0] # first UTF-8 byte as int
return 33 + (fb % 30)
def decode_char(i: int):
if i == 3: return " "
if i == 4: return ","
if i == 5: return "-"
if i == 6: return ":"
if 7 <= i <= 32: return chr(ord("a") + i - 7)
if i == 39: return "'"
if 33 <= i <= 38 or 40 <= i <= 62: return "?" # fallback bucket: unknown glyph
return None # control specials
def encode_caption(text: str):
"""text -> list of CAP_LEN ids, left-padded slot layout:
[<pad>...]<chars>. No <bos>/<sep> needed: the prefix encoder consumes the
whole 32-slot window positionally."""
ids = [encode_char(c) for c in text.lower()][:CAP_LEN]
return [PAD_ID] * (CAP_LEN - len(ids)) + ids
def decode_caption(ids) -> str:
out = []
for i in ids:
c = decode_char(int(i))
if c is not None:
out.append(c)
return "".join(out).strip()
# ---------------------------------------------------------------------------
# Generation model geometry (extremely tiny on purpose).
# ---------------------------------------------------------------------------
D_MODEL = 96
N_LAYERS = 4
N_HEADS = 4 # 24-dim heads
FF_HIDDEN = 256 # ffn_mult ~= 2.7 (lean by design)
SEQ_VIS = IMG * IMG # 256 autoregressive visual positions
POS_TOTAL = PREFIX_LEN + SEQ_VIS # 264 learned positions
SAMPLING = {"temperature": 0.85, "top_k": 10}
APPROX_PARAMS = 483_040 # exact: encoder + 4-block decoder + heads
|