Pico: A 48M-Parameter Agentic Language Model Trained from Scratch in JAX
Pico is a 48.2M-parameter decoder-only transformer trained from scratch in pure JAX (no Flax, no optax), following Karpathy's nanochat recipe and extending it to build a complete tool-calling agent from pretraining through on-policy reinforcement learningβwithout any distillation or synthetic LLM-generated data.
- W&B Report (Interactive Curves & Ablations): https://wandb.ai/mokshjain/full_model/reports/Pico:-A-48M-Parameter-Agentic-Language-Model-Trained-from-Scratch-in-JAX--VmlldzoxODExMjAwNQ==
- Final Checkpoint:
rl_web7(model.safetensors/runs/rl_web7/ckpt.npz) - Tokenizer:
v2(tokenizer.pkl/tokenizers/v2/tokenizer.pkl, 16,384-vocab byte-level BPE viarustbpe+tiktoken)
Highlights
- Agentic Pretraining (10B tokens,
final_10b_mila):- Trained on 80% ClimbMix web text + 20% code (
shell,python,sysadmin_qa), with the final 15% annealed on high-quality reference text (finepdfs_edu,fineweb_edu_hq,finemath,wikipedia) + 9% agentic tool-use trajectories (terminal_nemotron,terminal_openthoughts,exec_traces,nl2cmd_chat). - Reaches 0.1273 full CORE (beating GPT-2 124M's
0.114with 40% of the parameters) and 0.304 TOOL (72% 4-way NLβbash accuracy, 100% well-formed tool calls on terminal episodes).
- Trained on 80% ClimbMix web text + 20% code (
- Free Long-Context Extrapolation via Global NoPE (
final_10b_mila_16k_lr01):- Uses an
SSSLattention pattern: 7 local layers with a fixed 512-token sliding window + RoPE (base 10k), and 3 global causal layers (layers 3, 7, 9) with no positional encoding (NoPE). - Extrapolates zero-shot to 32k passkey retrieval (100% vs 20% for all-RoPE). A short 315M-token extension stage at 16,384 context lowers 8β16k FinePDFs-Edu loss from
0.918to0.847β0.853bpb and lifts 4-needle retrieval at 32k from0.18to0.75β0.76(full CORE0.1321β0.1343).
- Uses an
- Post-Training Without Distillation (
mt_tool20cβrl_web5βrl_web7):- Mid-training (0.9B tokens at 4k): Teaches multi-turn chat, math-as-python, and web/function-calling tool formats (
bash,python, Wikipediasearch/fetch,weather, declared JSON functions, and irrelevant-function refusals). - On-Policy GRPO RL (
rl_web7): Synchronous GRPO in fp32 over 8 real execution environments with exact trainerβengine logprob parity ($\max |\Delta \log p| \le 2 \times 10^{-4}$), std-normalized advantages, and reference-guided exploration (--guide, injecting ~230 programmatic reference solutions only into zero-pass groups to break the zero-variance plateau).
- Mid-training (0.9B tokens at 4k): Teaches multi-turn chat, math-as-python, and web/function-calling tool formats (
Available Checkpoints
Every pipeline stage is included in this repository under runs/<run_name>/ckpt.npz (weights + config metadata, ~193 MB each), alongside the root model.safetensors for rl_web7:
| Run Name | Stage | Context | Training | Key Metrics | Path |
|---|---|---|---|---|---|
rl_web7 |
Final Multi-Tool RL (Stage 2) | 4k (16k base) | +300 GRPO steps (8 envs incl. NL2SH) | Terminal 1.00 (clean unseen 0.333), NL2SH 0.303, GSM8K 0.145, QA w/ search 0.210, Weather 0.635, BFCL 0.33, CORE 0.131 | runs/rl_web7/ckpt.npz & model.safetensors |
rl_web5 |
Multi-Tool RL (Stage 1) | 4k (16k base) | 600 GRPO steps (7 envs, --guide) |
Terminal 1.00 (clean unseen 0.320), QA w/ search 0.201, Weather 0.560, BFCL 0.33, CORE 0.125 | runs/rl_web5/ckpt.npz |
mt_tool20c |
Chat + Tool Mid-Training | 4,096 | 0.9B tokens (LR Γ0.5, anneal mask) | Terminal 0.138, GSM8K 0.092, BFCL 0.32 (simple 0.41, irrelevance 0.58), CORE 0.123 | runs/mt_tool20c/ckpt.npz |
rl_mt05b |
Terminal + GSM8K RL | 4k (16k base) | 300 GRPO steps (terminal:1,gsm8k:1) |
Held-out terminal 0.558, GSM8K 0.165, NL2SH 0.267, CORE 0.120 | runs/rl_mt05b/ckpt.npz |
mt_lr05 |
Chat Mid-Training | 4,096 | 0.9B tokens (LR Γ0.5, anneal mask) | Held-out terminal 0.167, GSM8K 0.093, NL2SH 0.283, CORE 0.121 | runs/mt_lr05/ckpt.npz |
final_10b_mila_16k_lr01 |
16k Context Extension (LR Γ0.1) | 16,384 | 315M tokens (--long_doc_rows=True) |
Full CORE 0.1343, TOOL 0.297, 8β16k bpb 0.853, 4-needle@32k 0.76 | runs/final_10b_mila_16k_lr01/ckpt.npz |
final_10b_mila_16k |
16k Context Extension (LR Γ0.3) | 16,384 | 315M tokens (--long_doc_rows=True) |
Full CORE 0.1321, TOOL 0.293, 8β16k bpb 0.847, 4-needle@32k 0.75 | runs/final_10b_mila_16k/ckpt.npz |
final_10b_mila |
10B Pretrained Base | 2,048 | 10B tokens (38,146 steps, WSD + anneal) | Full CORE 0.1273, TOOL 0.304, ClimbMix val bpb 0.9090, shell_cmd bpb 0.707 |
runs/final_10b_mila/ckpt.npz |
End-to-End Evaluation Summary
All agent evaluations run greedily with real tool execution in isolated sandboxes (agent_eval.py). Open-domain QA uses the local 20.8M-passage Wikipedia BM25+rerank index (search_v2), and weather uses the strict single-temperature scorer:
| Metric | final_10b_mila_16k_lr01 (Base) |
mt_lr05 |
rl_mt05b |
mt_tool20c |
rl_web5 |
rl_web7 (Final) |
|---|---|---|---|---|---|---|
| Full CORE (22 tasks, centered) | 0.134 | 0.121 | 0.120 | 0.123 | 0.125 | 0.131 |
| Terminal (312 held-out instances) | 0.074 | 0.167 | 0.558 | 0.138 | 1.000 | 1.000 |
| Terminal_new (6 clean unseen task types) | β | 0.000 | 0.250 | β | 0.320 | 0.333 |
| NL2SH Execution Match (300) | 0.280 | 0.283 | 0.267 | β | 0.223 | 0.303 |
| GSM8K with Python Tool (1,319) | 0.002 | 0.093 | 0.165 | 0.092 | 0.137 | 0.145 |
| QA with Wikipedia Search (2,000) | β | 0.056 | 0.063 | 0.062 | 0.201 | 0.210 |
| QA Closed-Book (2,000) | β | 0.048 | 0.028 | 0.051 | 0.035 | 0.044 |
| Weather Value (strict) / No-Tool | β | 0 / 1.0 | 0 / 1.0 | 0.10 / 0.15 | 0.56 / 0.98 | 0.635 / 0.99 |
| BFCL Accuracy (Simple / Irrelevance) | β | 0.20 (0 / 1.0) | 0.19 (0 / 0.96) | 0.32 (0.41 / 0.58) | 0.33 (0.48 / 0.44) | 0.33 (0.46 / 0.48) |
| Chat No-End Rate (CLI Sampling) | β | 0.02 | 0.02 | 0.04 | 0.02 | 0.01 |
Quickstart
1. Interactive Terminal CLI with Tools
Clone the repository and run cli.py (automatically downloads rl_web7 and tokenizer v2 from mokshjain/pico on first run; works on CPU or GPU):
uv sync
uv run python cli.py --run_name=rl_web7
# Use live Wikipedia & Open-Meteo APIs instead of the local index / simulator:
uv run python cli.py --run_name=rl_web7 --live
2. Loading Any Stage in Python (via Repo)
import checkpoint
from tokenizer import Tokenizer
from engine import Engine
from agent import Agent, Episode
from cli import CLI_SAMPLING
from tools import Sandbox
import chat
# Automatically downloads from mokshjain/pico if not cached locally
params, cfg, meta = checkpoint.load_model(checkpoint.run_path("rl_web7"), dtype="float32")
tok = Tokenizer.load(cfg.tokenizer)
engine = Engine(params, cfg, n_slots=1, max_len=4096)
agent = Agent(engine, tok, CLI_SAMPLING)
with Sandbox() as sandbox:
seq_tokens = agent.prompt([
chat.system(chat.tools_prompt(["bash", "python", "search", "fetch", "weather"])),
chat.user("What is 2**32 - 1, and is it prime? Check with python."),
])
from engine import Sequence
ep = Episode(Sequence(seq_tokens), sandbox)
agent.run([ep])
print(tok.decode(ep.seq.tokens))
3. Loading model.safetensors Directly
import json, pickle
from huggingface_hub import hf_hub_download
from safetensors.numpy import load_file
tensors = load_file(hf_hub_download("mokshjain/pico", "model.safetensors"))
config = json.load(open(hf_hub_download("mokshjain/pico", "config.json")))
enc = pickle.load(open(hf_hub_download("mokshjain/pico", "tokenizer.pkl"), "rb"))
print("Loaded tensors:", len(tensors), "total params:", sum(x.size for x in tensors.values()))
Architecture Details
| Component | Shape | Parameters |
|---|---|---|
Token embedding wte (untied) |
16,384 Γ 512 |
8,388,608 |
Per layer: wq, wk, wv, wo |
4 Γ 512 Γ 512 |
1,048,576 |
Per layer: MLP w1, w2 (ReLUΒ², 4Γ expansion) |
512 Γ 2048 + 2048 Γ 512 |
2,097,152 |
10 transformer blocks (SSSL window pattern) |
31,457,280 |
|
Output head lm_head (untied) |
512 Γ 16,384 |
8,388,608 |
Per-layer residual / input-mixing scalars (resid_lambdas, x0_lambdas) |
2 Γ 10 |
20 |
| Total | 48,234,516 |
Forward pass (model.py):
Chat & Tool Special Tokens (chat.py)
<|bos|>[<|system_start|>...<|system_end|>]<|user_start|>task<|user_end|>
<|assistant_start|>optional text<|tool_start|>bash
<command><|tool_end|><|output_start|><output>
[exit code N]<|output_end|> ... final answer<|assistant_end|>
- Downloads last month
- 235