Training a small language model from scratch
More like getting Claude to train a small language model from scratch.
Training a small language model from scratch
Agents have dramatically changed how code is written, and consequently how a lot of computational research is done. This is particularly true for empirical deep learning research, where the quality of the research largely depends on the practical implementation. Like many other PhD students, I have been ceding more and more of the code to agents, largely because they are much faster and better at writing code and much less prone to the frustration of dealing with awkward errors, scattered compute and silent bugs. However, even though I had access to this incredible system which converts my ideas to working code, the scale and types of ideas I considered remained somewhat constrained.
A few weeks ago I decided to change this and try to increase the scope of problems I considered feasible working alone. I made an empty repository and asked Claude to help me set up the infrastructure to train a ~50M-parameter language model from scratch in JAX while being minimal in the spirit of nanochat
Around 98 hours after my initial prompt, I had a trained ~50M-parameter language model, Femto, which could do simple tasks in the terminal. The final model was pretrained on 10B web tokens followed by a length-extension phase and mid-training for agentic behavior. The model was modestly post-trained for writing basic shell scripts and Python code as well as some general tool calling. The infrastructure developed to train this model includes a clean JAX version of nanochat with a more optimized inference engine, faster RL training, a basic local search index, and a fast sandboxing system. I did not write a single line of code throughout the course of this project. Claude Code (with Opus 5.5) wrote the code for all the experiments and ran them, analyzed the results, suggested next steps, and also made the plots and visualizations in this post.
This post will mostly be a meta-analysis of this short experiment, talking about the process, decisions, and challenges along the way.
Femto in a Terminal
Before talking about the process, I think it is important to set expectations right. Femto is NOT a state-of-the-art model. I would personally say it is barely a good model. There are other models in the sub-1B parameter range which are definitely better in many ways. As an illustration, below are real transcripts of Femto v1 in the CLI, taken from the checks the agent ran on the final checkpoint: the prompts it used for every iteration, sampled with the CLI’s defaults. Search results and the weather response are abbreviated.
Challenges
While the examples make the model feel, for lack of a better word, underwhelming, there were several constraints that still made it an interesting exercise. First and most important was compute. I am fortunate to have access to decent enough compute for an academic setting, but in the grand scheme of things it still leaves a lot to be desired. For the most part I was constrained to single-node jobs (with 4 A100/H100s) across clusters (a budget of roughly 150 GPU-hours). cluv, a tool made by the incredible Mila IDT, made managing the compute easier but still needed adequate setup. Beyond compute, I also wanted to completely avoid any distillation. Distillation is an easy way to train small models given access to strong larger models. However, in the spirit of treating this as a pipeline of training a model from scratch, I decided to avoid relying on larger models in the pipeline.
Learning to Trust the Process
Let’s talk through the process of building this model. Most of my interactions were with a single “lead” session, but most of the work was delegated in pieces to different subagents. Each subagent operated on a different branch or worktree and was allowed to submit experiments to Slurm through cluv. The plans and research notes in the repository acted as shared memory between agents, and across the two session compactions.
Claude prepared a helpful breakdown of my “contributions”:
- Direction (20): the goal, the constraints, and scope changes.
- Decisions (23): choosing between options the agent laid out.
- Status checks (28): checking on the progress of various subagents and runs.
- Logistics (19).
Beyond high-level goals and design choices, I left much of the implementation to the agent. In my experience, it is much better to let the agent figure out the details rather than micromanaging them, as that is likely “out-of-distribution” relative to its largely autonomous training. The agent was quite a bit more rigorous in how it wrote code than I would be for a quick idea, but at some point it reached the “going overboard” stage where I had to ask it to do a complete cleanup. The final codebase ended up being around 9k LOC, with a lot of it being the eval, data, sandbox infra and inference engine.
Model design choices
To recap, there is nothing particularly special about the model; the goal of the exercise was mainly to see how much a relatively unsupervised agent can implement. Nevertheless, below is a summary of the model design and reasoning for certain choices made. (This section is mainly written by the agent and lightly edited by me.)
Design details
Base architecture
The starting point was Andrej Karpathy’s nanochat
The first baseline run was on the cluster within an hour of starting the project. With a 2.5B-token baseline pretraining corpus for faster iteration, it scored 0.116 on CORE
No positional encoding on the global layers
One of the first architectural questions that I wanted to investigate was the choice of positional encodings on the three global layers (while the seven sliding-window layers keep RoPE to handle local token order). The agent designed a small task to ablate the potential choices: RoPE
At the training length the arms tie, but beyond it RoPE degrades while NoPE does not: it retrieves the key at every depth out to 32k. We kept NoPE, which is also why the later 16k stage needed no RoPE rescaling
Code, and then agentic data
The next important question was how to give the base model some world knowledge while also making it a good base for tool-use training. The agent trained a code-aware tokenizer (12–29% better compression on shell and Python, 0.6% worse on web text, no change in loss) and did a sweep of the code fraction in the main pretraining mix.
The tool metrics keep improving with the code share in the mix, so the final run used 20%. In addition to the code mix in pretraining, the tool-calling abilities also came from the anneal (the last 15% of training) where the learning rate decays and the mix switches to higher-quality data
| Last 15% of training | CORE | TOOL | emits a tool call | well-formed |
|---|---|---|---|---|
| (a) keep the main mix | 0.106 | 0.157 | 0% | – |
| (b) high-quality text | 0.111 | 0.162 | 0% | – |
| (c) (b) plus 9% agentic data | 0.111 | 0.251 | 93% | 72% |
The agentic data consists of terminal-agent trajectories (Nemotron-Terminal-Corpus, OpenThoughts-Agent) compacted into Femto’s chat format, natural-language-to-bash pairs
Final pretraining run
The final pretraining run used NoPE global layers, the v2 tokenizer, 20% code and the agentic anneal, with a total of 10B tokens, and ran on two A100s in four hours. It reached CORE 0.127. A 315M-token stage at 16k context, chosen from eight arms (learning rate, document masking, training-time attention scaling, twice the tokens, 32k), made the loss keep falling with position out to 32k and lifted retrieval of four numbers hidden in real 32k-token documents from 0.18 to 0.76.
Because 20% of the pretraining mix went to code rather than web text, the base model’s headline CORE gain over the web-only 2.5B baseline came mostly from BoolQ, whose centered score swings with the model’s yes/no bias; without it, scaling to 10B mainly recovered the web-knowledge drop from adding code.
Tool use
The base model emitted well-formed tool calls in every terminal episode and solved 7% of the tasks. The agent planned the post-training pipeline from a literature review it ran while pretraining finished.
Mid-training (0.9B tokens of replay, agentic trajectories, chat, math written as Python
RL used vanilla GRPO
Beyond simple terminal use, I also wanted the model to be good at some general tool calling, including things like search and basic API calls, and four subagents built the required pieces in parallel: a BM25 index over 20.8M Wikipedia passages, a weather simulator with Open-Meteo’s response format, function-calling data from existing datasets
What did I learn?
While the final model and recipe is nothing special, being able to get to a working codebase written from scratch in such a short time is quite powerful. I have been able to push new ideas beyond what is discussed here and the model (and the infra around it) has only gotten better in the time that it took me to finally get to writing this post (some of these will probably appear here in future posts). The setup has also been useful to reproduce findings in the community at a smaller scale – validating its utility as a testbed.
Some of my takeaways from this experiment are below.
-
Agents are really really good at writing code. Something at this scale would have certainly taken me much longer to write from scratch by myself so they sped things up quite a lot. I am certainly not the only one to have noticed this, with results in even larger settings.
-
The current generation of agents lacks good experiment-design taste (and tends to set up many unnecessary experiments) and is very susceptible to getting stuck in dead ends. They latch onto things mentioned in passing and never revisit them unless explicitly prompted. The agent would take paper findings without any skepticism or uncertainty or if a cluster was down temporarily it would never consider checking and scheduling jobs unless prompted.
-
I did learn quite a bit about parts of the training stack that I was less familiar with, but I did end up with the feeling of having mostly been the audience in the agent’s show. If I had been working on the project more hands-on I would have certainly learned more about the details, but maybe would have missed part of the bigger picture. The trade-off is hard to quantify but it is hard not to feel that something was lost in this process.
-
Collaborating with humans is still a lot more fun than working alone with agents.