--- license: cc-by-nc-4.0 language: - en datasets: - HuggingFaceTB/smol-smoltalk - cais/mmlu - openai/gsm8k base_model: jkminder/d20_477m_seed2 base_model_relation: finetune pipeline_tag: text-generation library_name: transformers tags: - chat - sft - research - nanochat - scaling-ladder --- # Scaling Ladder — d20 (477M total parameters), seed 2, chat-SFT **Research artifact.** The chat-SFT of [d20_477m_seed2](https://huggingface.co/jkminder/d20_477m_seed2) — size d20, pretraining seed 2 of the plain-architecture Scaling Ladder (base models trained for 200 tokens per parameter). A small research model tuned for basic chat: helpfulness is limited by its size, and it has **no safety training**. **This revision (`main`)** mirrors `ds0_r1`, this seed's standard chat-SFT. ## Recipe One pass of nanochat's chat-SFT mixture, applied to the base repository's `main` revision (the 200-tokens-per-parameter model): [smol-smoltalk](https://huggingface.co/datasets/HuggingFaceTB/smol-smoltalk) (460K conversations) + MMLU auxiliary-train x3 + GSM8K main train x4 (with one calculator tool-call rendered per solution), interleaved by a fixed shuffle and then permuted by the revision's SFT data seed. The optimizer is a **cold start** (`+sft.load_optimizer=0`): fresh optimizer state, not the pretraining optimizer's. Learning rates start at 0.8x the pretraining values, no warmup, linear decay to zero over the second half; 467 steps at this size. Per-revision training provenance (cluster, code commit) is in the table below. ## Revisions Every revision is one chat-SFT run of the same base model: - **`ds`** — SFT data seed k: the permutation of the training-data order (all runs share the data; only the order differs). - **`r`** — replicate j: an independent repeat at identical configuration. The replicate index is never read by training, so repeats differ only through run-to-run (GPU) nondeterminism. - **`main`** mirrors `ds0_r1`, this seed's standard chat-SFT. Seed-1 repositories carry a noise battery (replicates `ds0_r1..r8`, data seeds `ds1..ds7` at `r1`) from a study of SFT run-to-run variance; the other seeds have `ds0_r1` only. Runs are added as they finish, so a missing revision only means it has not landed yet. | revision | step | SFT val bpb | ARC-Easy | ARC-Challenge | MMLU | trained on | code commit | |---|---|---|---|---|---|---|---| | ds0_r1 | 467 | 0.3001 | 0.6667 | 0.4676 | 0.3702 | bulbasaur | `fe20a844ca92` | Accuracies are fractions from each run's own chat_eval pass (full test suites, greedy decoding: temperature 0, 1 sample, 512 max new tokens; the same harness across all runs and sizes). "SFT val bpb" is the run's final validation loss (bits per byte) on the mixture's held-out split. A "-" means that run's eval has not landed yet. ## Anneal-mark chat-SFTs The base repository also holds the model annealed at every mark of its pretraining run: base revision `TPP_x` is the model annealed at x tokens per parameter (`TPP_200` is the base `main`). The revisions below apply the same chat-SFT recipe to each of those marks, one run per mark (SFT data seed 0, replicate 1). Their names carry the mark with three digits (`TPP_010` .. `TPP_180`); each row links the base revision it was trained from. These runs were trained at code commit `a06bf32`, which clamps the SFT learning-rate schedule so the last step cannot run at a negative learning rate; the TPP_200 chat-SFTs above predate that fix, and their one final step ran at a slightly negative learning rate (about -0.0004x the peak, read from a d12 run's log; the factor depends on the step count). | revision | base revision (annealed at) | base step | SFT step | SFT val bpb | trained on | |---|---|---|---|---|---| | TPP_010 | [`TPP_10`](https://huggingface.co/jkminder/d20_477m_seed2/tree/TPP_10) (10 tokens per parameter) | 3832 | 467 | 0.3174 | bulbasaur | | TPP_020 | [`TPP_20`](https://huggingface.co/jkminder/d20_477m_seed2/tree/TPP_20) (20 tokens per parameter) | 7832 | 467 | 0.3080 | bulbasaur | | TPP_030 | [`TPP_30`](https://huggingface.co/jkminder/d20_477m_seed2/tree/TPP_30) (30 tokens per parameter) | 12332 | 467 | 0.3100 | squirtle | | TPP_040 | [`TPP_40`](https://huggingface.co/jkminder/d20_477m_seed2/tree/TPP_40) (40 tokens per parameter) | 16332 | 467 | 0.3049 | squirtle | | TPP_060 | [`TPP_60`](https://huggingface.co/jkminder/d20_477m_seed2/tree/TPP_60) (60 tokens per parameter) | 24832 | 467 | 0.3033 | squirtle | | TPP_080 | [`TPP_80`](https://huggingface.co/jkminder/d20_477m_seed2/tree/TPP_80) (80 tokens per parameter) | 32832 | 467 | 0.3018 | squirtle | | TPP_100 | [`TPP_100`](https://huggingface.co/jkminder/d20_477m_seed2/tree/TPP_100) (100 tokens per parameter) | 41332 | 467 | 0.3005 | bulbasaur | | TPP_120 | [`TPP_120`](https://huggingface.co/jkminder/d20_477m_seed2/tree/TPP_120) (120 tokens per parameter) | 49332 | 467 | 0.2991 | bulbasaur | | TPP_140 | [`TPP_140`](https://huggingface.co/jkminder/d20_477m_seed2/tree/TPP_140) (140 tokens per parameter) | 57832 | 467 | 0.2978 | charmander | | TPP_160 | [`TPP_160`](https://huggingface.co/jkminder/d20_477m_seed2/tree/TPP_160) (160 tokens per parameter) | 66332 | 467 | 0.2970 | bulbasaur | | TPP_180 | [`TPP_180`](https://huggingface.co/jkminder/d20_477m_seed2/tree/TPP_180) (180 tokens per parameter) | 74332 | 467 | 0.2976 | charmander | ## Usage The chat template is bundled; format conversations with `apply_chat_template`: ```python from transformers import AutoModelForCausalLM, AutoTokenizer repo = "jkminder/d20_477m_seed2_sft" revision = "main" # or any revision above tok = AutoTokenizer.from_pretrained(repo, revision=revision, trust_remote_code=True) model = AutoModelForCausalLM.from_pretrained( repo, revision=revision, trust_remote_code=True, dtype="bfloat16") msgs = [{"role": "user", "content": "Why is the sky blue?"}] ids = tok.apply_chat_template(msgs, add_generation_prompt=True, return_tensors="pt") out = model.generate(ids, max_new_tokens=256) print(tok.decode(out[0, ids.shape[1]:], skip_special_tokens=True)) ``` `trust_remote_code=True` is required: the architecture matches no stock transformers class, so the modeling code ships in the repository (`modeling_nanochat_gpt.py`, plain PyTorch). Generation stops on `<|assistant_end|>`; sampling defaults (temperature 0.6, top_k 50) ship in `generation_config.json`. The template renders nanochat's chat format token-for-token (a leading system message is merged into the first user message). Every revision's upload is byte-verified against the converted checkpoint (hub listing sizes and content hashes); the conversion itself is verified on at least one revision per repository by chat-template, logit and loss equivalence against the original training code — a revision that was verified carries the record `verify_results.json`. ## Architecture, tokenizer, training data Identical to the base repository — a plain GPT (nanochat with all optional architecture mechanisms disabled), nanochat BPE tokenizer (32,768 tokens), base pretraining on ClimbMix; see [d20_477m_seed2](https://huggingface.co/jkminder/d20_477m_seed2) for the full description. Weights are bfloat16 safetensors, the training compute precision. ## License - Model weights: **cc-by-nc-4.0** (the base model mirrors its ClimbMix training data's research-only license, and this fine-tune mirrors the base). - Modeling/configuration code: MIT (derived from karpathy/nanochat; see the bundled LICENSE file).