Costi Claude Sonnet 5 commited on
Commit
b451f49
Β·
1 Parent(s): d9a3537

Add safetensors weights and standalone INFERENCE.py

Browse files

Exports the same 3 weight matrices already encoded in model.png to
model.safetensors via convert_to_safetensors.py, and adds INFERENCE.py
as a standalone entry point that loads from the safetensors file
instead of the PNG. Verified byte-identical output between main.py
and INFERENCE.py for the same prompt. README documents both paths.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

Files changed (4) hide show
  1. INFERENCE.py +77 -0
  2. README.md +26 -4
  3. convert_to_safetensors.py +47 -0
  4. model.safetensors +3 -0
INFERENCE.py ADDED
@@ -0,0 +1,77 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ INFERENCE.py β€” run PixelModel from its model.safetensors weights.
3
+
4
+ This is the standalone entry point for anyone who just wants to load the
5
+ safetensors weights and generate an image, without needing model.png or
6
+ the rest of this repo's training code.
7
+
8
+ Usage:
9
+ python INFERENCE.py "a red circle"
10
+ python INFERENCE.py "a red circle" --model model.safetensors --out out.png --scale 8
11
+ """
12
+
13
+ import argparse
14
+ import os
15
+ import sys
16
+
17
+ import numpy as np
18
+ import torch
19
+ from PIL import Image
20
+ from safetensors.torch import load_file
21
+
22
+ PROMPT_DIM = 32
23
+ OUT_SIZE = 32
24
+
25
+
26
+ def prompt_to_embedding(prompt: str) -> torch.Tensor:
27
+ """Deterministic char-level embedding -> PROMPT_DIM vector."""
28
+ vec = torch.zeros(PROMPT_DIM)
29
+ for i, ch in enumerate(prompt.lower()):
30
+ idx = i % PROMPT_DIM
31
+ vec[idx] += ord(ch) / 127.0
32
+ norm = vec.norm()
33
+ if norm > 0:
34
+ vec = vec / norm
35
+ return vec
36
+
37
+
38
+ def forward(weights: dict, prompt: str) -> torch.Tensor:
39
+ emb = prompt_to_embedding(prompt)
40
+ x = torch.tanh(weights["W1"] @ emb)
41
+ x = torch.tanh(weights["W2"] @ x)
42
+ x = torch.sigmoid(weights["W3"] @ x)
43
+ return x.reshape(OUT_SIZE, OUT_SIZE, 3)
44
+
45
+
46
+ def generate(prompt: str, model_path: str, out_path: str, scale: int = 8):
47
+ if not os.path.exists(model_path):
48
+ sys.exit(
49
+ f"Model not found: {model_path}\n"
50
+ f"Run: python convert_to_safetensors.py to create one from model.png first."
51
+ )
52
+
53
+ weights = load_file(model_path)
54
+
55
+ with torch.no_grad():
56
+ result = forward(weights, prompt)
57
+
58
+ arr = (result.numpy() * 255).clip(0, 255).astype(np.uint8)
59
+ img = Image.fromarray(arr, mode="RGB")
60
+
61
+ if scale > 1:
62
+ img = img.resize((OUT_SIZE * scale, OUT_SIZE * scale), Image.NEAREST)
63
+
64
+ img.save(out_path)
65
+ print(f"prompt : '{prompt}'")
66
+ print(f"model : {model_path} ({os.path.getsize(model_path)} bytes, safetensors)")
67
+ print(f"output : {out_path} ({OUT_SIZE * scale}x{OUT_SIZE * scale} px)")
68
+
69
+
70
+ if __name__ == "__main__":
71
+ p = argparse.ArgumentParser(description="PixelModel inference (safetensors)")
72
+ p.add_argument("prompt", help="Text prompt")
73
+ p.add_argument("--model", default="model.safetensors", help="Path to safetensors weights")
74
+ p.add_argument("--out", default="out.png", help="Output image path")
75
+ p.add_argument("--scale", type=int, default=8, help="Upscale factor for output (default 8 -> 256x256)")
76
+ args = p.parse_args()
77
+ generate(args.prompt, args.model, args.out, args.scale)
README.md CHANGED
@@ -40,6 +40,19 @@ All weights live inside `model.png`.
40
 
41
  ---
42
 
 
 
 
 
 
 
 
 
 
 
 
 
 
43
  ## πŸ§ͺ Dataset vs Outputs
44
 
45
  | Target | Output |
@@ -56,10 +69,13 @@ All weights live inside `model.png`.
56
  ## πŸ“ Files
57
 
58
  ```text
59
- model.png ← THE MODEL (64Γ—3200 px)
60
- main.py ← inference
61
- train.py ← training
62
- model.py ← architecture
 
 
 
63
  dataset/
64
  red.png
65
  red.txt ← prompt: "red"
@@ -76,8 +92,14 @@ python train.py --epochs 500 --lr 0.05
76
 
77
  python main.py "red"
78
  python main.py "a cat" --out cat.png --scale 8
 
 
 
 
79
  ```
80
 
 
 
81
  ---
82
 
83
  ## πŸ“Š Tips
 
40
 
41
  ---
42
 
43
+ ## πŸ“¦ Standard weights (safetensors)
44
+
45
+ `model.png` is the canonical model β€” training writes to it directly, and it's what makes PixelModel PixelModel. For tooling that expects standard weight files, the same 3 matrices are also exported as `model.safetensors` (202,752 parameters total, no bias terms):
46
+
47
+ ```bash
48
+ python convert_to_safetensors.py # model.png -> model.safetensors
49
+ python convert_to_safetensors.py --model model.png --out model.safetensors
50
+ ```
51
+
52
+ Re-run this after training if you retrain into a new `model.png` β€” `model.safetensors` doesn't update itself.
53
+
54
+ ---
55
+
56
  ## πŸ§ͺ Dataset vs Outputs
57
 
58
  | Target | Output |
 
69
  ## πŸ“ Files
70
 
71
  ```text
72
+ model.png ← THE MODEL (64Γ—3200 px)
73
+ model.safetensors ← same weights, standard format (generated, see below)
74
+ main.py ← inference, loads model.png
75
+ INFERENCE.py ← inference, loads model.safetensors
76
+ convert_to_safetensors.py ← model.png -> model.safetensors
77
+ train.py ← training
78
+ model.py ← architecture
79
  dataset/
80
  red.png
81
  red.txt ← prompt: "red"
 
92
 
93
  python main.py "red"
94
  python main.py "a cat" --out cat.png --scale 8
95
+
96
+ # equivalent, but loads model.safetensors instead of model.png
97
+ python convert_to_safetensors.py
98
+ python INFERENCE.py "a cat" --out cat.png --scale 8
99
  ```
100
 
101
+ `main.py` and `INFERENCE.py` produce byte-identical output for the same prompt β€” they're the same architecture and weights, just loaded from different files.
102
+
103
  ---
104
 
105
  ## πŸ“Š Tips
convert_to_safetensors.py ADDED
@@ -0,0 +1,47 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ convert_to_safetensors.py β€” extract PixelModel's weights from model.png
3
+ and save them as model.safetensors.
4
+
5
+ model.png remains the canonical "weights ARE the image" artifact (see
6
+ README.md); this just re-exports its contents in a standard, directly
7
+ loadable format for tooling that expects .safetensors weights.
8
+
9
+ Usage:
10
+ python convert_to_safetensors.py
11
+ python convert_to_safetensors.py --model model.png --out model.safetensors
12
+ """
13
+
14
+ import argparse
15
+ from safetensors.torch import save_file
16
+
17
+ from model import load_model, pixels_to_weights
18
+
19
+
20
+ def convert(model_path: str, out_path: str):
21
+ pixels = load_model(model_path)
22
+ W1, W2, W3 = pixels_to_weights(pixels)
23
+
24
+ save_file(
25
+ {
26
+ "W1": W1.contiguous(),
27
+ "W2": W2.contiguous(),
28
+ "W3": W3.contiguous(),
29
+ },
30
+ out_path,
31
+ metadata={
32
+ "format": "pt",
33
+ "source": model_path,
34
+ "architecture": "PixelModel 3-layer MLP (char-embed -> tanh -> tanh -> sigmoid)",
35
+ },
36
+ )
37
+
38
+ total = W1.numel() + W2.numel() + W3.numel()
39
+ print(f"Wrote {out_path} ({total:,} parameters: W1={tuple(W1.shape)}, W2={tuple(W2.shape)}, W3={tuple(W3.shape)})")
40
+
41
+
42
+ if __name__ == "__main__":
43
+ p = argparse.ArgumentParser(description="Export PixelModel weights (from model.png) to safetensors")
44
+ p.add_argument("--model", default="model.png", help="Path to source model PNG")
45
+ p.add_argument("--out", default="model.safetensors", help="Output safetensors path")
46
+ args = p.parse_args()
47
+ convert(args.model, args.out)
model.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:8c365a7f71955241d343da88796e7c221f88b040e93006e9f1a46ecf5b36f10a
3
+ size 811344