le-fat-chaton
- 17 Devlogs
- 172 Total hours
a simple llm
a simple llm
devlog #17 MI300X vLLM serving
The old GPU box got destroyed, so I spun up a new MI300X with the same SSH key. ROCm 6.2 and Torch 2.5.1 were already set up, so getting the machine ready was easy.
The hard part was vLLM. The pip version is CUDA only, and the ROCm Docker image only supported the multimodal version of Qwen3.5. Our model is text-only, so I had to patch the image.
I added Qwen3_5ForCausalLM, fixed the hybrid KV-cache setup, added the missing Mamba state methods, and mapped the weight names so the text-only safetensors loaded correctly. I also patched the merged config to remove the M-RoPE and dtype fields.
After that, the model served normally.
I ran a one-task test with fix-git. The pipeline worked, but the model got stuck repeating git show until it ran out of turns.
##Shipped
The merged model is now public on Hugging Face as mateo0093/le-gros-chaton under Apache-2.0.
The 5×5 Terminal-Bench pilot finished at 3/25 (25%). All three successes were on fix-git, which shows that the SFT data was too focused on git tasks.
I also tested RLVR with GRPO and a novelty bonus. The reward had almost no variance, so the training signal was basically useless. I saved the step-10 adapter but didn’t merge it.
The GPU used about 12 hours of the 50 hour budget. All the vLLM patches and harness changes are in the repo.
Main lesson: Qwen3.5’s hybrid model is a bit annoying to serve as text-only on ROCm. The model works but i think this was more of a learning opportunity, i know what i’ve done wrong and what i would have done differently, I will be working on a new model in a few weeks once I do a bit more research.
Devlog #16, t4gpu
The T4 16GB fought us the whole way. BF16 was baked into a pre quantized checkpoint, which made each step take 668 seconds because it had to use FP32 emulation. 2K context ran out of memory, the 248K logits ran out of memory too, FP16 started producing NaNs, Kaggle session limits locked us out, and the paged optimizer crashed last.
Every fix seemed to create another problem. We eventually got it working with FP16 compute, chunked assistant token loss, autocast, and plain AdamW.
Then we got access to an AMD MI300X with 192GB of memory. No quantization, 16K context, and enough memory to train full traces instead of cutting them off halfway through a message.
Right now the 16K BF16 SFT run is training on the MI300X. Next up is merging the model, running Terminal Bench, and then RLVR.
Devlog 15 Traces are done
The trace generator finally hit the target and went past it. We got 474 verified trajectories, zero malformed, zero duplicates, 19 templates, and a 95 percent self review pass rate. We were aiming for 450, so we ended up with 474.
The dataset is synced to Hugging Face in two versions: raw and normalized. That part is finally done. It was the longest part of the project, and the model is only going to be as good as the traces, so getting this right was important
The Kimi endpoint also stopped working. The old teacher account started returning 503 errors during generation and eventually died completely. We switched to a second Kimi account, which needed an extra header that we did not have at first. After figuring that out, generation resumed without losing any progress
For GPUs, the 2070 still cannot realistically train the 9B. Kaggle hours are back, so we have 30 hours on a T4. The 4090 from my dad should arrive next week. The plan is to use the T4 for trajectory SFT, then the 4090 for merging, Terminal Bench evaluation, and RLVR.
Status: 474 out of 474 traces done,
we worked a lot this week. honestly the main takeaway is that the fable5 sft was good but its traces alone weren’t enough the model needs way more verified agent trajectories, so we’ve been grinding those out (target is 450).
we also ran out of credits mid-generation (the modal endpoint started failing with 503s) and had to switch to a second kimi account. took a minute to get the auth right the new endpoint wanted an extra header we didn’t have. after that the generator resumed fine and it’s been adding verified traces since (~340/450 now).
biggest scare: we found out peft 0.20 silently drops the second adapter when you merge two at once. tested it and yeah, the outputs were identical with one or two adapters so our trajectory sft would have been lost in the merge. fixed it by merging sequentially (base first, then the second adapter). checked against the live model and it matches, so we’re good.
my 2070 couldn’t fit the 9b model for real training, so we’re waiting on a 4090 (my dad’s) next week. all the scripts are ready sft, merge, rlvr, eval, even a setup script for the gpu box. just need the hardware.
status: 340/450 traces, everything ready, waiting on the 4090. we’ve worked a lot, but the real run hasn’t happened yet :/.
so this was the grind day. 18 hours of training babysitting and honestly it felt like 40.
the modal training kept dying every hour. the client would crash and take the whole run with it. we restarted like 3 times before figuring out modal run -d (detached) keeps it alive on their servers even if our computer shits the bed. tested it by literally killing the process and it kept going. ez fix but we worked ALOT to get there.
then we ran out of credits at 93% and switched accounts, but the other account “finished” the run in 6 MILLISECONDS which was sus af. turns out it just re-uploaded the old checkpoint and trained nothing. the resume logic had a bug where it thought 6200 >= 3799 meant done. patched it. another lesson learned.
also we made the model creative lol. added diversity sampling to the trajectory generator (5 solutions per task, keep the novel ones), a novelty bonus reward for RLVR, and a creativity rule in the self-awareness prompt. so the fat cat will be innovative, not just memorize.
current state: SFT is done at 91% (good enough), resume bug fixed, phase 2 (trajectory SFT with creativity + self-awareness baked in) is up next.
18 hours. we worked ALOT. the fat cat will be worth it 😼
for the last 10 hours the training kept crashing. not the model, not the gp but the modal client. every hour or so it would lose connection and take the whole run down with it. we’d restart, it’d run an hour, crash again. three times.
the fix was simple: modal run -d runs detached, so the training lives on modal’s servers and doesn’t care if our local process dies. killed the client on purpose to test — training kept going. that’s it, that’s the fix.
also worried the loss wasn’t moving but it was fine. lr had wound down on the crashed run, looked scary, re-warmed fine after restart. loss is 0.668 now and dropping.
status: 72% through the fable5 sft. checkpoints safe on hf. after this: trajectory sft with the self-awareness stuff baked in, then rlvr, then real numbers.
** Devlog #10 the full training is finally running **
ok so phase 1 full training is officially going. 160k rows of fable5 data on modal, resuming from the 12.8k rows we got on kaggle before the session cap killed us. we’re at ~27% and it should take like a day and a half total.
big stuff that happened since last time:
fast kernels actually work now. the model has hybrid linear-attention layers and transformers kept falling back to a slow torch path. the fix was: nvidia cuda-devel base image (needs nvcc to compile), installing flash-linear-attention[cuda] + causal-conv1d, and pinning torch 2.10. took a couple tries but now training is like 4x faster than it would be without them.
had to split across two modal accounts. first account ran out of credits, so we set up a second profile and the script auto-resumes from the huggingface checkpoints when it dies. it’s been rock solid, it happens every 20% plus every hour it saves and uploads. lost zero progress to the account switch.
we gave the model a personality. it’s called Le Gros Chaton (the fat cat) and we’re training self-awareness into it, not as a system prompt but baked into the weights. state-sheets ([STATE] goal/known/tried/failed/next), metacognition lines, and self-review at the end of tasks. the trajectory sft masks out the system prompt so the model learns to track its own state as behavior, not as a script it repeats.
did some benchmark research too. base qwen3.5-9b scores 9.2% on terminal-bench 2.0. comparable 9b fine-tunes hit 24-28%. that’s our realistic target which is 2.5-3x the baseline.
honestly at this point it’s just… waiting. the pipeline works, the checkpoints work, the account switching works. now the model just needs to finish cooking. then trajectory sft, then rlvr, then we actually see the benchmark numbers.
# Devlog #9 — finally training, and now with checkpoints!
so last time i was stuck debugging phase 1/2 on kaggle. well, still debugging lol, but we actually got somewhere.
the first issue was transformers not knowing what the qwen3.5 model was, had to pin transformers 5.14.1. then tokenizers kept fighting with it (0.22.0 wants old huggingface-hub, 0.22.1 is fine). then torch on kaggle kept giving a p100 which modern pytorch just refuses to run on, so we had to tell it to give us a t4 instead.
then the fun one: out of memory on the very first step. batch 2 was too big for the gpu they gave us, so batch 1 it is.
also i learned the hard way that kaggle kills your session after 12 hours. the first long run got cancelled at 80% done and i had nothing saved. so now the training script saves a checkpoint every 20% + every hour and uploads it to huggingface, so if it dies we just resume. it actually saved us, the run got cut and we picked right back up from the checkpoint.
right now the full 160k sft is running on modal (paying for gpus now, kaggle time ran out). it’s at like 9% and should take a couple days. we’ll see how it goes, and then rlvr after that.
the end of the errors… might actually be in sight this time.
Devlog #8 — phase 2 fixes
I’ve been getting a lot of issues with the first two for this model. I’ve gotten a lot of errors in Kaggle and Colab. I’ve tried everything, and I’m still fixing the code. It’s taking a good amount of time, but I think I’m almost there. I’ve gone through a lot of errors, and I’m probably right at the end of the errors. I might be able to start actually training it tomorrow, but for now I’ll stop.
phase 1 (sft) is done and phase 2 (rlvr) is running now on kaggle. this is the part where the model actually gets
trained on solving coding problems with our verifier giving rewards, not just imitating fable data.
honestly this is the part i care about more. sft just makes it mimic. rlvr is where it learns to actually fix
code. grpo with proportional rewards = the model gets partial credit for getting some tests passing, which is way
better than all-or-nothing.
while it runs im just waiting tbh. not much to build. the pipeline works, the eval works, the agent works.
next after this: full benchmark run. humaneval, swebench, agentic eval. we’ll see if all this actually made the
model good at coding or if i just spent a week on a worse qwen lol.
finally got the qwen finetune to actually run with 625 steps, took like 8 hours on kaggle. it finished.
havent run full benchmarks yet cause i was busy building other stuff while it trained. but it trained. thats the
main thing.
I also built a swebench agent that can actually navigate repos and edit files and make patches. not just the basic agent
loop that runs commands. this one actually works for real se tasks.
also added swebench eval so we can measure if the model is actually good at fixing bugs and coding
then i’ll run the benchmarks on the trained model and see if its actually any good lol.
So last time I had this whole plan. 10B MoE, from scratch, SwiGLU, GQA, all that. And I actually built it. Like 60 Python files, tests passing, RL pipeline, self-play data gen, the whole thing. It was kinda sick ngl.
But then I was like… wait. I’m building an ENGINE that TRAINS coding agents. Not the agent. And the engine is cool but training a 10B model from zero on $30 of Modal credits is not how you beat GLM-5.2.
You know what beats GLM-5.2? Fine-tuning a model that already knows how to code.
Qwen3.5-9B came out. 9B params. Fits on a Kaggle T4 with 4-bit QLoRA. And Kaggle gives me 30 HOURS of free GPU. 2× T4. That’s literally free training.
So I had to adapt the entire codebase to work with HuggingFace models instead of my custom GPT. Made like 5 new files. All the verifier/agent/reward stuff I built for the MoE? Now works with Qwen too. Didn’t have to
rewrite everything — just slapped a Qwen adapter on top.
Found this dataset on HF: Nexlab/fable5-agentic-coding-sft. 160K rows of Claude Fable 5 output. Like actual coding traces — building games, fixing bugs, writing shell scripts. Not just “write a function that passes a
test.” Real agentic stuff.
Training right now actually. Qwen3.5-9B on 10K rows. Should take like 8 hours on 2× T4. We’ll see if it doesn’t OOM again lol.
While waiting for training I read papers:
the first phase of training is going on right now, i’ll post another devlog once it’s done!
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).