Fine-tuning a small language model for RAG in a browser on a Mac A fine-tuned Qwen3-0.6B model is now the default answer engine on the author's blog's /ask/ page after four rounds of training data, matching the base model's accuracy while producing roughly one-third as many wrong answers and never answering questions the blog does not cover. The author trained the model with LoRA via mlx-lm on an M4 Mac mini with 24 GB of memory, using 4,500 training examples drawn from 2,193 openly licensed documents from the Go blog, Rust blog, Rust RFCs and Python PEPs, with Claude Sonnet 5.5 writing the questions and Claude Opus 5.5 answering them; each version trained for one epoch over 2,265 iterations in about 1 hour 20 minutes. The model, dataset and code are public, and the fine-tune is also a smaller download than the base model, which scored 17 of 30 faithful answers when exported in the same 4-bit format. Last week https://bart.degoe.de/rag-with-a-small-language-model-in-the-browser/ we tried to summarize semantic search results https://bart.degoe.de/ask/ with small language models running in your browser. They weren’t great at it, hallucinating often. But a 0.6B-parameter model doesn’t need to know everything, it just needs to read four passages, and either summarize them or say they don’t answer the question. “That sounds like a fine-tuning problem, and maybe a follow-up post.” So here’s that follow-up. It took four rounds of training data to get there, and the ones that didn’t work were just as interesting. The short version: the fine-tuned Qwen3-0.6B is now the default on ask my blog https://bart.degoe.de/ask/ . It’s right about as often as the original, it’s wrong a third as often, and it never answered a question the blog doesn’t cover. It’s also a smaller download. The model, the dataset and the code are all public, so you can put it on your own site do-it-yourself and try for yourself. The job, and the plan the-job-and-the-plan The model gets a system prompt “answer in at most two sentences, using only these passages, or say the blog doesn’t seem to cover it” , four numbered passages that the semantic search found, and a question. That’s it. A good answer either states what the passages say, or says “The blog does not seem to cover this.” when they don’t answer it. For fine-tuning a model that can do that we need a few thousand examples that show it how to do that job well. I don’t have a few thousand blog posts, so this is the plan: 1. Training data from openly licensed technical writing. My blog’s 45 eval questions from the last post are the test set, so training on my posts would basically be cheating there are only 16 of them anyway . Instead I used the Go blog https://go.dev/blog , the Rust blog https://blog.rust-lang.org , the Rust RFCs https://rust-lang.github.io/rfcs/ and the Python PEPs https://peps.python.org : 2,193 documents of technical writing, which all have a very permissible license to use them. 2. Same retrieval mechanism. Each document is chunked and embedded exactly like the semantic search on this blog https://bart.degoe.de/semantic-search-in-your-browser/ , and each training question gets the top four passages that search returns. So the model trains on passages that are as noisy as the ones it gets in production: sometimes the answer is in passage 1, sometimes in passage 4, sometimes a near-duplicate from a different post sits next to it. 3. Have Claude write examples. Claude Sonnet 5.5 wrote the questions, about a single passage. Some have the answer among the four chunks, for some I removed the document the question came from so it’ll train on refusing to answer , and some are near-misses: related questions the passages don’t really answer. Claude Opus 5.5 then answered every one of them based on the chunks, using the same system prompt the blog uses. Technically , that makes this a form of distillation: a big model demonstrates the job, a small one learns to imitate it