sprout-instruct / export.py
GeorgeCodesSometimes's picture
Finalize public documentation and link the live browser story workshop
559972f verified
Raw History Blame Contribute Delete
7.45 kB
"""Export instruction-tuned weights and a standalone CPU demo, without uploading.
python export.py --checkpoint checkpoints/sft/best.pt --out checkpoints/release
"""
import argparse
import json
from pathlib import Path
import shutil
import subprocess
import torch
from safetensors.torch import save_model
from eval import load as load_checkpoint
from inference import FIELDS, TEMPLATE, digest, load
def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--checkpoint", default="checkpoints/sft/best.pt")
parser.add_argument("--data", default="data")
parser.add_argument("--out", default="checkpoints/release")
args = parser.parse_args()
out = Path(args.out)
if out.exists() and any(out.iterdir()):
raise ValueError("Output directory is not empty. Use a new directory.")
torch.set_num_threads(4)
model, tok, cfg, step = load_checkpoint(args.checkpoint, args.data, "cpu")
checkpoint = torch.load(args.checkpoint, map_location="cpu", weights_only=False)
if "sft" not in checkpoint or checkpoint["sft"]["template"] != TEMPLATE:
raise ValueError("Export requires an instruction-tuned checkpoint with the story template.")
out.mkdir(parents=True, exist_ok=True)
save_model(model, str(out / "model.safetensors"))
shutil.copyfile(Path(args.data) / "tokenizer.model", out / "tokenizer.model")
architecture = {key: cfg[key] for key in ("n_layer", "dim", "n_head", "ctx", "vocab")}
architecture["pos"] = cfg.get("pos", "rope")
root = Path(__file__).resolve().parent
commit = subprocess.check_output(["git", "rev-parse", "HEAD"], cwd=root, text=True).strip()
config = dict(format_version=1, model=architecture, template=TEMPLATE, fields=FIELDS,
special_tokens=tok.special, dtype="float32", step=step,
source_checkpoint_sha256=digest(args.checkpoint), source_commit=commit,
training_fingerprint=checkpoint["fingerprint"],
instruction_revision=checkpoint["sft"]["revision"],
sha256={name: digest(out / name) for name in ("model.safetensors", "tokenizer.model")})
(out / "config.json").write_text(json.dumps(config, indent=2) + "\n")
for name in ("model.py", "tokenizer.py", "inference.py", "app.py", "requirements.txt", "LICENSE"):
shutil.copyfile(root / name, out / name)
params = sum(p.numel() for p in model.parameters())
run = checkpoint.get("wandb_id")
card = f"""---
license: mit
language:
- en
datasets:
- roneneldan/TinyStories
- roneneldan/TinyStoriesInstruct
pipeline_tag: text-generation
tags:
- pytorch
- safetensors
- custom-code
---
# Sprout story model
A decoder-only model trained from scratch on TinyStoriesV2-GPT4 and instruction tuned on TinyStoriesInstruct. It has {params:,} parameters, {cfg['n_layer']} layers, width {cfg['dim']}, {cfg['n_head']} attention heads, a {cfg['ctx']}-token context, {architecture['pos']} positions, RMSNorm, SwiGLU, tied embeddings, and a {cfg['vocab']}-token byte-level BPE vocabulary.
[Try the live browser demo](https://huggingface.co/spaces/GeorgeCodesSometimes/sprout). The static demo runs float32 ONNX weights on your device; prompts stay local.
This bundle contains float32 inference weights at instruction step {step:,}. It excludes optimizer state, training data, credentials, and training dependencies. The tied embedding/output tensor is stored once and restored with `safetensors.torch.load_model`. `config.json` records weight and tokenizer SHA-256 checksums, source checkpoint SHA-256, source code commit, and the original training fingerprint.
## Run locally
Download all files from this model repository into an empty directory, then run:
```bash
python -m pip install -r requirements.txt
python app.py
```
Use the included loading module directly:
```python
from inference import load, stream
model, tok, config = load(".")
for text in stream(model, tok, {{"Summary": "A little fox learns to be patient."}}):
print(text, end="\\r", flush=True)
```
`load` also accepts a Hub model ID and an optional `revision` commit. The included code is the loading contract; this is not a Transformers `AutoModel` repository. Loading does not require any training corpus files. The demo uses the exact training template, keeps the full instruction in context, and supports streamed output, stopping, temperature, and top-k.
## Training and evaluation
The source checkpoint's instruction validation loss is {checkpoint['best_val']:.6f}. The [training run](https://wandb.ai/george-nasseem/sprout/runs/{run}) records configuration and losses. See the [project README](https://github.com/gnasseem/sprout#instruction-tuning) for the fixed behavioral evaluation, comparison with the pretrained model, and base-validation regression. Low validation loss does not establish reliable constraint following.
Pretraining uses the story-level seeded split in the project, with 537,686,596 training tokens and 5,402,089 validation tokens. Instruction preparation excludes malformed and duplicate stories, held-out story overlap, and conversations beyond the context limit. TinyStoriesInstruct is pinned to `{config['instruction_revision']}`. These are synthetic English stories generated by GPT models, not a general conversational corpus.
## Limitations and intended use
An educational story-generation experiment. It often misses requested words and sentences, repeats itself, and loses narrative details. Instruction tuning regresses base-story validation loss. Long instructions leave less room for the story. It has no general chat or factual-answering capability, no content filter, and no suitability evaluation for children. Outputs can contain biases or disturbing story content from its synthetic source corpus.
## License and provenance
Code and exported weights use the included MIT license. The upstream [TinyStories dataset card](https://huggingface.co/datasets/roneneldan/TinyStories) lists CDLA-Sharing-1.0; [TinyStoriesInstruct](https://huggingface.co/datasets/roneneldan/TinyStoriesInstruct) is the separate instruction corpus. No source stories are redistributed in this bundle. The dataset creators' [paper](https://arxiv.org/abs/2305.07759) describes TinyStories.
"""
(out / "README.md").write_text(card, encoding="utf-8")
(out / "SPACE.md").write_text("""---
title: Sprout Story Workshop
sdk: gradio
sdk_version: 6.30.0
python_version: '3.11'
app_file: app.py
pinned: false
license: mit
---
# Sprout Story Workshop
An experimental small story model. It can miss constraints, repeat itself, and lose story details.
""")
restored, restored_tok, _ = load(out)
prefix = tok.encode(TEMPLATE.format(instruction="Write a short story.\nSummary: A fox finds a seed."))
x = torch.tensor([prefix])
before = model.generate(x, 64, temperature=0)
after = restored.generate(x, 64, temperature=0)
assert torch.equal(before, after), "export changed greedy generation"
assert model.head.weight is model.tok_emb.weight
assert restored.head.weight is restored.tok_emb.weight
assert restored_tok.decode(after[0].tolist()) == tok.decode(before[0].tolist())
print(f"Exported {params:,} parameters; 64 greedy tokens match; tied weights and tokenizer match.")
print(f"Bundle: {out.resolve()}; weights: {(out / 'model.safetensors').stat().st_size:,} bytes")
if __name__ == "__main__":
main()