BlakePeavy commited on
Commit
4e8cb8d
Β·
verified Β·
1 Parent(s): 6bdf000

Deploy BitPixelLM Gradio Space

Browse files
Files changed (1) hide show
  1. app.py +189 -189
app.py CHANGED
@@ -1,190 +1,190 @@
1
- """
2
- BitPixelLM β€” Hugging Face Gradio Space
3
-
4
- Generates 32x32 pixel art from text prompts using BitPixelLM,
5
- a 1.58-bit ternary transformer built from scratch with BitNet b1.58.
6
- """
7
-
8
- import sys
9
- import json
10
- import torch
11
- import numpy as np
12
- import gradio as gr
13
- from pathlib import Path
14
- from PIL import Image
15
-
16
- HERE = Path(__file__).parent
17
- sys.path.insert(0, str(HERE))
18
-
19
- from model.tokenizer import PaletteTokenizer
20
- from model.text_encoder import TextTokenizer, TextEncoder
21
- from model.bit_pixel_decoder import BitPixelLMDecoder, BitPixelLM
22
-
23
- # ─── Paths (all relative to this file β€” bundled in the Space) ──
24
- PALETTE_PATH = HERE / "palette_256.npy"
25
- VOCAB_PATH = HERE / "vocab.json"
26
- CHECKPOINT_PATH = HERE / "best.pt"
27
-
28
- device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
29
- model = None
30
- palette_tok = None
31
- text_tok = None
32
-
33
-
34
- def load_all():
35
- global model, palette_tok, text_tok
36
-
37
- palette_tok = PaletteTokenizer(palette_path=str(PALETTE_PATH))
38
- with open(VOCAB_PATH) as f:
39
- vocab = json.load(f)
40
- text_tok = TextTokenizer(vocab)
41
-
42
- ckpt = torch.load(str(CHECKPOINT_PATH), map_location=device, weights_only=False)
43
- args = ckpt.get("args", {})
44
-
45
- text_encoder = TextEncoder(
46
- vocab_size=text_tok.vocab_size,
47
- d_model=args.get("d_model", 256),
48
- nhead=args.get("nhead", 8),
49
- num_layers=args.get("text_layers", 3),
50
- dim_feedforward=args.get("dim_ff", 512),
51
- max_seq_len=args.get("max_text_len", 32),
52
- dropout=args.get("dropout", 0.1),
53
- )
54
- pixel_decoder = BitPixelLMDecoder(
55
- vocab_size=palette_tok.vocab_size,
56
- d_model=args.get("d_model", 256),
57
- nhead=args.get("nhead", 8),
58
- num_layers=args.get("pixel_layers", 6),
59
- dim_feedforward=args.get("dim_ff", 512),
60
- img_size=32,
61
- dropout=args.get("dropout", 0.1),
62
- )
63
- m = BitPixelLM(text_encoder, pixel_decoder).to(device)
64
- m.load_state_dict(ckpt["model_state_dict"])
65
- m.eval()
66
- model = m
67
- print(f"BitPixelLM loaded | device={device} | vocab={text_tok.vocab_size} words")
68
-
69
-
70
- def generate(prompt, temperature, top_k, top_p, num_samples, scale):
71
- if not prompt.strip():
72
- raise gr.Error("Please enter a prompt.")
73
-
74
- words = prompt.lower().split()
75
- unknown = [w for w in words if w not in text_tok.word2idx
76
- and w not in ("<pad>", "<sos>", "<eos>", "<unk>")]
77
-
78
- text_tokens = text_tok.encode(prompt).unsqueeze(0).to(device)
79
- images = []
80
- try:
81
- for _ in range(int(num_samples)):
82
- with torch.no_grad():
83
- toks = model.generate(
84
- text_tokens,
85
- sos_token=palette_tok.sos_token,
86
- eos_token=palette_tok.eos_token,
87
- temperature=temperature,
88
- top_k=int(top_k),
89
- top_p=top_p,
90
- )
91
- arr = palette_tok.decode_tokens(toks[0].cpu().tolist())
92
- img = Image.fromarray(arr, "RGB")
93
- s = int(scale)
94
- if s > 1:
95
- img = img.resize((32 * s, 32 * s), Image.NEAREST)
96
- images.append((img, None))
97
- except Exception as e:
98
- raise gr.Error(f"Generation error: {e}")
99
-
100
- if unknown:
101
- gr.Warning("Unknown words (mapped to <unk>): " + ", ".join(unknown))
102
- return images
103
-
104
-
105
- # ─── Vocabulary for the info panel ──────────────────────────────
106
- with open(VOCAB_PATH) as _f:
107
- _vocab_raw = json.load(_f)
108
- VOCAB_DISPLAY = ", ".join(sorted(w for w in _vocab_raw if not w.startswith("<")))
109
-
110
- EXAMPLES = [
111
- "a red pixel art sword",
112
- "a blue pixel art knight",
113
- "a green pixel art dragon",
114
- "a purple pixel art wizard",
115
- "a gold pixel art crown",
116
- "a dark pixel art skeleton",
117
- "a teal pixel art ship",
118
- "a orange pixel art fox",
119
- "a pink pixel art butterfly",
120
- "a yellow pixel art beehive",
121
- ]
122
-
123
- with gr.Blocks(
124
- title="BitPixelLM β€” Pixel Art Generator",
125
- theme=gr.themes.Soft(),
126
- css="""
127
- .gallery-item img { image-rendering: pixelated !important; }
128
- #gallery img { image-rendering: pixelated !important; }
129
- """,
130
- ) as demo:
131
- gr.Markdown(
132
- """
133
- # 🎨 BitPixelLM β€” Pixel Art Generator
134
- ### Generate 32Γ—32 pixel art from text prompts
135
-
136
- **BitPixelLM** is a custom 1.58-bit ternary transformer (BitNet b1.58) trained from scratch to
137
- produce pixel art sprites. 7.4M parameters, 75% ternary weights, best val loss 0.4015.
138
-
139
- Try prompts like: `a red pixel art sword` Β· `a blue pixel art knight` Β· `a green pixel art dragon`
140
- """
141
- )
142
-
143
- with gr.Row():
144
- with gr.Column(scale=1):
145
- prompt_box = gr.Textbox(
146
- label="Prompt",
147
- placeholder="a red pixel art sword",
148
- lines=2,
149
- )
150
- with gr.Row():
151
- btn = gr.Button("Generate ✨", variant="primary", scale=2)
152
- n_samples = gr.Slider(1, 8, value=4, step=1, label="Samples")
153
-
154
- with gr.Accordion("Advanced", open=False):
155
- temperature = gr.Slider(0.1, 2.0, value=0.8, step=0.05, label="Temperature")
156
- top_k = gr.Slider(0, 256, value=40, step=1, label="Top-K")
157
- top_p = gr.Slider(0.1, 1.0, value=0.9, step=0.05, label="Top-P")
158
- scale = gr.Slider(1, 16, value=8, step=1,
159
- label="Upscale factor",
160
- info="8Γ— = 256Γ—256 output")
161
-
162
- gr.Markdown(f"**Supported words:** {VOCAB_DISPLAY}")
163
-
164
- with gr.Column(scale=2):
165
- gallery = gr.Gallery(
166
- label="Generated Pixel Art",
167
- columns=4, rows=2, height=520,
168
- object_fit="contain", elem_id="gallery",
169
  )
170
-
171
- gr.Examples(examples=EXAMPLES, inputs=[prompt_box], label="Try an example")
172
-
173
- gr.Markdown(
174
- """
175
- ---
176
- **How it works:** pixel art generation is framed as language modelling.
177
- Each of the 1,024 pixels in a 32Γ—32 image is predicted as a discrete token
178
- from a 256-color palette, left-to-right, top-to-bottom, conditioned on
179
- the text prompt via cross-attention.
180
- [Model card](https://huggingface.co/BlakePeavy/BitPixelLM) Β·
181
- [Paper: BitNet b1.58](https://arxiv.org/abs/2402.17764)
182
- """
183
- )
184
-
185
- inputs = [prompt_box, temperature, top_k, top_p, n_samples, scale]
186
- btn.click(fn=generate, inputs=inputs, outputs=gallery)
187
- prompt_box.submit(fn=generate, inputs=inputs, outputs=gallery)
188
-
189
- load_all()
190
- demo.launch(server_name="0.0.0.0", server_port=7860)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ BitPixelLM β€” Hugging Face Gradio Space
3
+
4
+ Generates 32x32 pixel art from text prompts using BitPixelLM,
5
+ a 1.58-bit ternary transformer built from scratch with BitNet b1.58.
6
+ """
7
+
8
+ import sys
9
+ import json
10
+ import torch
11
+ import numpy as np
12
+ import gradio as gr
13
+ from pathlib import Path
14
+ from PIL import Image
15
+
16
+ HERE = Path(__file__).parent
17
+ sys.path.insert(0, str(HERE))
18
+
19
+ from model.tokenizer import PaletteTokenizer
20
+ from model.text_encoder import TextTokenizer, TextEncoder
21
+ from model.bit_pixel_decoder import BitPixelLMDecoder, BitPixelLM
22
+
23
+ # ─── Paths (all relative to this file β€” bundled in the Space) ──
24
+ PALETTE_PATH = HERE / "palette_256.npy"
25
+ VOCAB_PATH = HERE / "vocab.json"
26
+ CHECKPOINT_PATH = HERE / "best.pt"
27
+
28
+ device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
29
+ model = None
30
+ palette_tok = None
31
+ text_tok = None
32
+
33
+
34
+ def load_all():
35
+ global model, palette_tok, text_tok
36
+
37
+ palette_tok = PaletteTokenizer(palette_path=str(PALETTE_PATH))
38
+ with open(VOCAB_PATH) as f:
39
+ vocab = json.load(f)
40
+ text_tok = TextTokenizer(vocab)
41
+
42
+ ckpt = torch.load(str(CHECKPOINT_PATH), map_location=device, weights_only=False)
43
+ args = ckpt.get("args", {})
44
+
45
+ text_encoder = TextEncoder(
46
+ vocab_size=text_tok.vocab_size,
47
+ d_model=args.get("d_model", 256),
48
+ nhead=args.get("nhead", 8),
49
+ num_layers=args.get("text_layers", 3),
50
+ dim_feedforward=args.get("dim_ff", 512),
51
+ max_seq_len=args.get("max_text_len", 32),
52
+ dropout=args.get("dropout", 0.1),
53
+ )
54
+ pixel_decoder = BitPixelLMDecoder(
55
+ vocab_size=palette_tok.vocab_size,
56
+ d_model=args.get("d_model", 256),
57
+ nhead=args.get("nhead", 8),
58
+ num_layers=args.get("pixel_layers", 6),
59
+ dim_feedforward=args.get("dim_ff", 512),
60
+ img_size=32,
61
+ dropout=args.get("dropout", 0.1),
62
+ )
63
+ m = BitPixelLM(text_encoder, pixel_decoder).to(device)
64
+ m.load_state_dict(ckpt["model_state_dict"])
65
+ m.eval()
66
+ model = m
67
+ print(f"BitPixelLM loaded | device={device} | vocab={text_tok.vocab_size} words")
68
+
69
+
70
+ def generate(prompt, temperature, top_k, top_p, num_samples, scale):
71
+ if not prompt.strip():
72
+ raise gr.Error("Please enter a prompt.")
73
+
74
+ words = prompt.lower().split()
75
+ unknown = [w for w in words if w not in text_tok.word2idx
76
+ and w not in ("<pad>", "<sos>", "<eos>", "<unk>")]
77
+
78
+ text_tokens = text_tok.encode(prompt).unsqueeze(0).to(device)
79
+ images = []
80
+ try:
81
+ for _ in range(int(num_samples)):
82
+ with torch.no_grad():
83
+ toks = model.generate(
84
+ text_tokens,
85
+ sos_token=palette_tok.sos_token,
86
+ eos_token=palette_tok.eos_token,
87
+ temperature=temperature,
88
+ top_k=int(top_k),
89
+ top_p=top_p,
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
90
  )
91
+ arr = palette_tok.decode_tokens(toks[0].cpu().tolist())
92
+ img = Image.fromarray(arr, "RGB")
93
+ s = int(scale)
94
+ if s > 1:
95
+ img = img.resize((32 * s, 32 * s), Image.NEAREST)
96
+ images.append((img, None))
97
+ except Exception as e:
98
+ raise gr.Error(f"Generation error: {e}")
99
+
100
+ if unknown:
101
+ gr.Warning("Unknown words (mapped to <unk>): " + ", ".join(unknown))
102
+ return images
103
+
104
+
105
+ # ─── Vocabulary for the info panel ──────────────────────────────
106
+ with open(VOCAB_PATH) as _f:
107
+ _vocab_raw = json.load(_f)
108
+ VOCAB_DISPLAY = ", ".join(sorted(w for w in _vocab_raw if not w.startswith("<")))
109
+
110
+ EXAMPLES = [
111
+ "a red pixel art sword",
112
+ "a blue pixel art knight",
113
+ "a green pixel art dragon",
114
+ "a purple pixel art wizard",
115
+ "a gold pixel art crown",
116
+ "a dark pixel art skeleton",
117
+ "a teal pixel art ship",
118
+ "a orange pixel art fox",
119
+ "a pink pixel art butterfly",
120
+ "a yellow pixel art beehive",
121
+ ]
122
+
123
+ with gr.Blocks(
124
+ title="BitPixelLM β€” Pixel Art Generator",
125
+ theme=gr.themes.Soft(),
126
+ css="""
127
+ .gallery-item img { image-rendering: pixelated !important; }
128
+ #gallery img { image-rendering: pixelated !important; }
129
+ """,
130
+ ) as demo:
131
+ gr.Markdown(
132
+ """
133
+ # 🎨 BitPixelLM β€” Pixel Art Generator
134
+ ### Generate 32Γ—32 pixel art from text prompts
135
+
136
+ **BitPixelLM** is a custom 1.58-bit ternary transformer (BitNet b1.58) trained from scratch to
137
+ produce pixel art sprites. 7.4M parameters, 75% ternary weights, best val loss 0.4015.
138
+
139
+ Try prompts like: `a red pixel art sword` Β· `a blue pixel art knight` Β· `a green pixel art dragon`
140
+ """
141
+ )
142
+
143
+ with gr.Row():
144
+ with gr.Column(scale=1):
145
+ prompt_box = gr.Textbox(
146
+ label="Prompt",
147
+ placeholder="a red pixel art sword",
148
+ lines=2,
149
+ )
150
+ with gr.Row():
151
+ btn = gr.Button("Generate ✨", variant="primary", scale=2)
152
+ n_samples = gr.Slider(1, 8, value=4, step=1, label="Samples")
153
+
154
+ with gr.Accordion("Advanced", open=False):
155
+ temperature = gr.Slider(0.1, 2.0, value=0.8, step=0.05, label="Temperature")
156
+ top_k = gr.Slider(0, 256, value=40, step=1, label="Top-K")
157
+ top_p = gr.Slider(0.1, 1.0, value=0.9, step=0.05, label="Top-P")
158
+ scale = gr.Slider(1, 16, value=8, step=1,
159
+ label="Upscale factor",
160
+ info="8Γ— = 256Γ—256 output")
161
+
162
+ gr.Markdown(f"**Supported words:** {VOCAB_DISPLAY}")
163
+
164
+ with gr.Column(scale=2):
165
+ gallery = gr.Gallery(
166
+ label="Generated Pixel Art",
167
+ columns=4, rows=2, height=520,
168
+ object_fit="contain", elem_id="gallery",
169
+ )
170
+
171
+ gr.Examples(examples=EXAMPLES, inputs=[prompt_box], label="Try an example")
172
+
173
+ gr.Markdown(
174
+ """
175
+ ---
176
+ **How it works:** pixel art generation is framed as language modelling.
177
+ Each of the 1,024 pixels in a 32Γ—32 image is predicted as a discrete token
178
+ from a 256-color palette, left-to-right, top-to-bottom, conditioned on
179
+ the text prompt via cross-attention.
180
+ [Model card](https://huggingface.co/BlakePeavy/BitPixelLM) Β·
181
+ [Paper: BitNet b1.58](https://arxiv.org/abs/2402.17764)
182
+ """
183
+ )
184
+
185
+ inputs = [prompt_box, temperature, top_k, top_p, n_samples, scale]
186
+ btn.click(fn=generate, inputs=inputs, outputs=gallery)
187
+ prompt_box.submit(fn=generate, inputs=inputs, outputs=gallery)
188
+
189
+ load_all()
190
+ demo.launch(server_name="0.0.0.0", server_port=7860)