le-fat-chaton
- 4 Devlogs
- 8 Total hours
a simple llm
a simple llm
Devlog #4 — wait, why am I training on Wikipedia?
Last week I got le gros chaton (my from-scratch transformer) to actually train. Ran it on wikitext-103 in a Colab notebook on a free T4, 12k steps, val loss hit 3.73. Felt insane for something I wrote myself from PyTorch primitives.
But then I looked at what it generated. It could do English-ish. It knew about Singapore and stuff. The moment I asked it to write code? Total garbage. Fair enough. It has never seen a single line of code.
the pivot
I thought about what I actually want this thing to do. Answer: a coding agent. Something that mogs on terminal-bench, that you give a task and it runs commands, fixes its own bugs, gets it done. Qwen-coder territory.
Wikitext won’t get me there. You can’t learn Python from Wikipedia articles about the snake.
So the plan changed. The big project is now le fat chaton, the bigger sibling. And it’s a coder.
the architecture stuff
I went down a rabbit hole on what frontier coding models do. They’re basically all mixture-of-experts (MoE) now. Qwen3-coder is 480B params / 35B active. Deepseek-coder-v2-lite is 16B / 2.4B active. The trick:
Runs like a small model, knows like a big one. Exactly what I want for a snappy terminal agent.
So I rewrote the model. Added an MoE class. A gate routes each token to its top-2 experts out of 8, with a load-balance loss so the router doesn’t pick the same expert every time. Also threw in:
Fat config math: ~10.25B total / ~3.65B active. Not building that locally, it would crash my PC again.
the data question
Wikitext was a fine proof. For the coder I need code. Options:
Going with starcoderdata Python, blended with ~15-20% cosmopedia (prose) so it has general knowledge. That blend is the Qwen-coder recipe.
am I warm-starting from wikitext?
No. Different architecture entirely (17M dense vs 10B MoE, weights don’t fit). Even if they did, warm-starting a coder from prose wastes compute unlearning prose. Fresh random init on the mixed corpus. Clean slate.
This went from “lets make a little model that talks” to “lets build a coding agent that mogs the big labs” real fast. Ambitious for a solo student with a 2070 and $30 of Modal credits? Absolutely. Doing it anyway? Yeah.
Next: the eval harness so I can measure if this is making the model better at code (pass@1, pass@5 on HumanEval). Val loss going down means nothing if it still can’t write a
DEVLOG #3: teaching it to talk (fine-tuning)
the base model was a completer. ask it “what is 2+2?” and it just continues the sentence. fine-tuning is what makes it actually answer: same model, more training, but on prompt/response pairs.
i wrote ~48 Q&A pairs by hand. trivia, math, jokes, hellos. all in the same rigid Q: …\nA: format. new trick: loss masking. only the response counts toward the loss, not the prompt, so i set prompt targets to -100 (pytorch’s cross_entropy ignores that automatically, zero model changes). tested the mask boundary in isolation first. wrong mask = 400 steps of training the wrong thing.
the run: load base weights, 400 steps, lower lr. loss went 12.3 to 0.02.then the reality check. it nails everything in the set. capitals, math, jokes, “who are you,” verbatim. but something NOT in the set? slop. 17M params + 48 examples is a lookup table, not a reasoner. real chat models are billions of params. not a bug, just the scale.but it’s finally coherent. the first base model was word salad. now you type a question and get a real answer back that makes sense.
it’s tiny and dumb and can’t generalize, but it actually talks. that’s the win.
full pipeline done: tokenizer, model from scratch, pretrain on wikipedia, fine-tune, chat. mine, it talks, i understand every piece.
DEVLOG #2: real data, real loss, and a lesson about memorizing
split my data into 90% train / 10% val. before i only had one number, training loss, and it lies to you. you can’t tell learning from memorizing with just that. now i check both every 200 steps. rule: both go down = learning. train down but val up = memorizing = useless.also made the model bigger, 7M -> 17.7M params. immediately ran out of gpu memory lol. dropped batch size to 32 to fit my 8GB card.training: 3000 steps. loss started at 158 (should be ~10 for random , my first pass blew up and made garbage). recovered fast though, didn’t ruin the run.
right now the llm is spitting out gibberish and random words but after finetuning it should sound more coherent
DEVLOG #1: wiki text training
so i finally got the model to train on actual data instead of that one paragraph about singapore nature reserves lol. before this the whole thing was basically a joke, i trained a 7M parameter transformer on like 134 tokens and it just spat out jumbled fragments of the same sentence over and over. cool proof it worked but definitely not a “language model” in any real sense.anyway. the big move this week was swapping out the tiny json file for wikitext-2. that’s a huggingface dataset of wikipedia text, about 2.4 million tokens once you filter out the empty lines. that’s… a lot more than 134 lol. encoding it took maybe half a minute with tiktoken and the whole tensor fits on my gpu no problem (it’s only like 8MB of ints).