NanoAgent-15M

A 15M-parameter Llama-style decoder-only transformer for chat and structured tool calling, trained end-to-end on a single RTX 3050 Laptop GPU (4GB VRAM). Built for local edge deployment (~58MB per fp32 checkpoint).

  • Full training code + data pipeline: https://github.com/sanjayram-a/nanoagent-15m
  • Interactive training curves for every run below live under this repo's Training metrics tab

This is a 15M model. Grade the format (stops at <|im_end|>, well-formed <tool_call>{…}</tool_call> blocks, stays on topic a sentence or two), not GPT-level polish.

Model details

Architecture Llama-style decoder-only (transformers.LlamaForCausalLM)
Hidden / layers / heads 384 / 8 / 6 Q + 2 KV (GQA 3:1), SwiGLU 1024
Context length 1024 tokens (prompt + reply combined)
Vocab 6,144 byte-level BPE, custom-trained (nanoagent_tokenizer/)
Positional encoding RoPE (theta 10,000); RMSNorm; tied word embeddings
Parameters 15M (58MB fp32 .pt)
Chat format <|im_start|>{role}\n{content}<|im_end|>; tools in <tools>…</tools>; calls in <tool_call>{…}</tool_call>; results in <tool_response>…</tool_response>

Checkpoints in this repo

File Variant Val loss Val ppl
runs/nanoagent_15m/final_15m_model.pt Base (pretrained, no SFT β€” use with chat.py --raw) 3.9772 53.37
runs/nanoagent_15m/best_model.pt Base, best-val snapshot 3.9791 ~53.5
runs/nanoagent-15m-sft/sft_chat_max_final.pt SFT run 1 (blended 409,760-row mix) β€” recommended chat + tool weights 1.7348 5.67
runs/nanoagent-15m-sft2/checkpoints/latest.pt SFT run 2 / instruct, full training checkpoint (model_state + optimizer) 1.4783 4.39
nanoagent_tokenizer/ Tokenizer (tokenizer.json, tokenizer_config.json) β€” required by every checkpoint β€” β€”

Base pretraining β€” final_15m_model.pt

Trained tokens (read from TensorBoard + training_summary.json)

Metric Value
Tokens seen 999,948,288 (~1B)
Optimizer steps 15,256 (batch 16 Γ— 1024 Γ— grad-accum 4 = 65,536 tok/step)
Stage 1 β†’ Stage 2 3,051 steps @ 200M tokens β†’ remainder @ 800M tokens
Train loss 8.18 β†’ 4.02
Val loss 6.19 β†’ 3.98 (best 3.9791, final 3.9772, ppl 53.37)
Throughput ~37–38K tok/s on RTX 3050 Laptop 4GB
LR schedule warmup 1,000 steps to 6e-4, cosine decay to 6e-5
Interruption crashed once at step 8,239 (inf grad norm, fp16 overflow), resumed from emergency checkpoint to completion

Datasets (from curriculum_meta.json + run log)

Packed to train.bin (1,000,000,000 tokens, 2000MB) + val.bin (10,000,000 tokens, 20MB). Acquisition targets below include +15% selection and +15% acquisition margins; the sampler never shuffles across the 200M-token stage boundary.

Source HF dataset (config / split) Train target (tokens) Val target (tokens) Stage 1 share Stage 2 share
l3 openbmb/Ultra-FineWeb-L3 (Ultra-FineWeb-L3-en-Multi-Style-Synthetic / train) 409,975,000 3,967,500 35% 30%
fineweb HuggingFaceFW/fineweb-edu (sample-10BT / train, score β‰₯ 3) 357,075,000 3,306,250 35% 25%
cosmo HuggingFaceTB/smollm-corpus (cosmopedia-v2 / train) 330,625,000 3,306,250 25% 25%
code openbmb/UltraData-Code (UltraData-Code-L2 / py) 224,825,000 2,645,000 5% 20%

Documents selected: 12,102 validation / 251,506 stage-1 / 969,804 stage-2.

SFT run 1 β€” sft_chat_max_final.pt (recommended)

Trained tokens (read from TensorBoard)

Metric Value
Tokens seen 409,544,800 (~410M, ~1 epoch over the mix)
Optimizer steps 12,500
Train loss 6.62 β†’ 1.70
Val loss 2.36 β†’ 1.79 (final 1.7348, ppl 5.67)

Datasets (409,760 raw rows, single blended pass + 15% tool-context negatives)

Source HF dataset (config) Rows Share
instruct HuggingFaceTB/smoltalk (smol-magpie-ultra) 150,000 36.6%
worldqa HuggingFaceTB/ultrachat_questions_about_world 86,000 21.0%
xlam Salesforce/xlam-function-calling-60k (gated β€” needs an accepted-terms token) 60,000 14.6%
glaive glaiveai/glaive-function-calling-v2 47,500 11.6%
summarize HuggingFaceTB/smoltalk (smol-summarize) 34,000 8.3%
rewrite HuggingFaceTB/smoltalk (smol-rewrite) 30,000 7.3%
chat HuggingFaceTB/smoltalk (everyday-conversations) 2,260 0.6%

Tool-negative injection: 15% of non-tool rows keep their plain-text answer but get an unrelated tool list attached, teaching that tools in context don't always need calling.

SFT run 2 / instruct β€” checkpoints/latest.pt

Trained tokens (read from TensorBoard)

Metric Value
Tokens seen 895,970,816 (~896M)
Optimizer steps 27,350
Train loss 6.63 β†’ 1.53
Val loss 2.19 β†’ 1.48 (latest 1.4783, ppl 4.39)
LR schedule warmup 200 steps to 5e-5, cosine decay to 5e-6, grad-accum 4, 8192-token batch budget

Datasets (on-disk tokenized cache ./sft_cache: 380,573 train / 1,939 val rows, 3 epochs)

Source HF dataset Row cap
instruct HuggingFaceTB/smoltalk (smol-magpie-ultra) 200,000
glaive glaiveai/glaive-function-calling-v2 90,000
xlam Salesforce/xlam-function-calling-60k (gated) 60,000
summarize HuggingFaceTB/smoltalk (smol-summarize) 40,000
rewrite HuggingFaceTB/smoltalk (smol-rewrite) 40,000
chat HuggingFaceTB/smoltalk (everyday-conversations) 2,260
worldqa β€” (excluded from this run) 0

Tool-negative ratio 0.10. Lowest validation numbers of all three runs.

Usage

Chat script + tool definitions live in the GitHub repo (chat.py, tools.json). Checkpoints here load with the same architecture:

# Chat + tools (SFT weights)
python chat.py --model_path runs/nanoagent-15m-sft/sft_chat_max_final.pt --tools tools.json

# Single-shot test
python chat.py --model_path runs/nanoagent-15m-sft/sft_chat_max_final.pt \
  --tools tools.json --prompt "What is the weather in Paris?" \
  --max_new_tokens 40 --temperature 0

# Base weights (never saw the chat template β€” use --raw)
python chat.py --model_path runs/nanoagent_15m/final_15m_model.pt --raw --prompt "Hello world"

Minimal transformers loading (matches train.py / chat.py exactly)

# Minimal transformers loading (matches train.py / chat.py exactly)
from transformers import LlamaConfig, LlamaForCausalLM, PreTrainedTokenizerFast
import torch

config = LlamaConfig(vocab_size=6144, hidden_size=384, intermediate_size=1024,
                     num_hidden_layers=8, num_attention_heads=6, num_key_value_heads=2,
                     max_position_embeddings=1024, rms_norm_eps=1e-5,
                     rope_theta=10000.0, tie_word_embeddings=True)
model = LlamaForCausalLM(config)
state = torch.load("runs/nanoagent-15m-sft/sft_chat_max_final.pt", map_location="cpu")
model.load_state_dict(state["model_state"] if "model_state" in state else state)
tokenizer = PreTrainedTokenizerFast.from_pretrained("nanoagent_tokenizer")

Limitations

  • 15M parameters / 1024-token context: short, simple exchanges only; weak factuality.
  • Tool-call format is the skill being taught β€” always validate emitted JSON before executing.
  • xlam-derived rows are ~9% malformed upstream (skipped + counted during SFT).
  • No reasoning/scratchpad tokens by design at this size; no QK-Norm (stock Llama has none).

Reproducing / full logs

The GitHub repo contains the full pipeline (train-tokenizer.py, download_and_pack.py, train.py, sft_chat.py, sft_common.py, sft_agent_openbmb.py, sft-chat-max.py), the raw run notebook (note.md), per-run TensorBoard events (runs/*/events.out.tfevents.*, mirrored in the Training metrics tab), and training_config.json / training_summary.json for the base run.

Downloads last month

-

Downloads are not tracked for this model. How to track
Inference Providers NEW
This model isn't deployed by any Inference Provider. πŸ™‹ Ask for provider support

Evaluation results

  • final validation loss on NanoAgent 1B-token pretraining mix (Ultra-FineWeb-L3 + FineWeb-Edu + Cosmopedia-v2 + UltraData-Code)
    self-reported
    3.977
  • final validation perplexity on NanoAgent 1B-token pretraining mix (Ultra-FineWeb-L3 + FineWeb-Edu + Cosmopedia-v2 + UltraData-Code)
    self-reported
    53.370
  • final validation loss on Blended SFT mix, 409,760 rows (smoltalk + ultrachat world-QA + xlam-function-calling-60k + glaive-function-calling-v2 + summarize + rewrite)
    self-reported
    1.735
  • final validation perplexity on Blended SFT mix, 409,760 rows (smoltalk + ultrachat world-QA + xlam-function-calling-60k + glaive-function-calling-v2 + summarize + rewrite)
    self-reported
    5.670
  • latest validation loss on Instruct-heavy SFT mix, 380,573 tokenized train rows (smol-magpie-ultra + glaive + xlam + summarize + rewrite + chat)
    self-reported
    1.478
  • latest validation perplexity on Instruct-heavy SFT mix, 380,573 tokenized train rows (smol-magpie-ultra + glaive + xlam + summarize + rewrite + chat)
    self-reported
    4.390