File size: 7,448 Bytes
70a25ae 559972f 70a25ae | 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 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 | """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()
|