stanno / scripts /train_stanno_on_embeddings.py
oldman-dev's picture
Up-to-date with original repo
8f0d906 verified
Raw
History Blame Contribute Delete
3.87 kB
"""
Train a STANNO autoencoder on pre-computed CLIP embeddings.
Run generate_clip_embeddings.py first to produce the .npy file, then run
this script to train and save the STANNO. The resulting .pkl file can be
loaded into ComfyUI via the STANNOLoad node.
Usage:
python scripts/train_stanno_on_embeddings.py \
--embeddings style_embeddings.npy \
--out stanno_clip_style.pkl \
[--hidden 256] \
[--epochs 300] \
[--lr 0.005] \
[--trainer fixed]
The input/output dimension is inferred automatically from the embedding file
(typically 768 for SD 1.5 / ViT-L-14 CLIP).
"""
from __future__ import annotations
import argparse
import pickle
import sys
from pathlib import Path
import numpy as np
def parse_args() -> argparse.Namespace:
p = argparse.ArgumentParser(description="Train a STANNO autoencoder on CLIP embeddings")
p.add_argument("--embeddings", required=True, help="Path to .npy file of shape (N, dim)")
p.add_argument("--out", required=True, help="Output .pkl path for the trained STANNO")
p.add_argument("--hidden", type=int, default=256,
help="Hidden layer width (default 256 → [dim, 256, dim])")
p.add_argument("--extra-hidden", type=int, default=0,
help="Add a second hidden layer of this width (0 = disabled)")
p.add_argument("--epochs", type=int, default=300, help="Training epochs (default 300)")
p.add_argument("--batch-size", type=int, default=32)
p.add_argument("--lr", type=float, default=0.005, help="Learning rate")
p.add_argument("--trainer", default="fixed",
choices=["fixed", "local_rule", "evolutionary"])
return p.parse_args()
def main() -> None:
args = parse_args()
embeddings_path = Path(args.embeddings)
if not embeddings_path.is_file():
print(f"File not found: {embeddings_path}")
sys.exit(1)
embeddings = np.load(str(embeddings_path)).astype(np.float32)
n, dim = embeddings.shape
print(f"Loaded {n} embeddings of dim={dim} from {embeddings_path}")
# Build layers list
layers = [dim, args.hidden]
if args.extra_hidden > 0:
layers.append(args.extra_hidden)
layers.append(dim)
print(f"Architecture: {layers}")
# Import STANNO (add repo root to path if needed)
repo_root = str(Path(__file__).parent.parent)
if repo_root not in sys.path:
sys.path.insert(0, repo_root)
from stanno.config.schema import STANNOConfig
from stanno.core.stanno import STANNO
config = STANNOConfig(
layers=layers,
trainer_type=args.trainer,
learning_rate=args.lr,
)
stanno = STANNO(config)
report_every = max(1, args.epochs // 10)
def log_cb(epoch: int, loss: float) -> None:
if (epoch + 1) % report_every == 0:
print(f" epoch {epoch + 1:5d} / {args.epochs} loss={loss:.5f}")
print(f"\nTraining STANNO ({args.trainer}) for {args.epochs} epochs …")
stanno.fit(
embeddings,
embeddings,
epochs=args.epochs,
batch_size=args.batch_size,
callback=log_cb,
)
out_path = Path(args.out)
out_path.parent.mkdir(parents=True, exist_ok=True)
with open(str(out_path), "wb") as f:
pickle.dump(stanno, f)
# Quick sanity check
preds = stanno.predict(embeddings[:8])
mse = float(np.mean((preds - embeddings[:8]) ** 2))
print(f"\nFinal MSE on first 8 samples: {mse:.5f}")
print(f"Saved trained STANNO → {out_path}")
print("\nNext steps:")
print(" 1. Load in ComfyUI: STANNO Loader node → model_path =", out_path)
print(" 2. For Dream Conditioning: connect to 'STANNO Dream Conditioning' node")
print(" 3. For Dynamic LoRA: connect to 'STANNO Dynamic LoRA' node")
if __name__ == "__main__":
main()