Lab
Blue Book
Running at homeReading quizzes generated from my kid’s actual books by a local model on a GPU at home.
Blue Book
Chapter 1: Down the Rabbit-Hole Question 3 of 10Why did Alice run across the field after the White Rabbit?
- AShe wanted to give back the watch he had dropped.
- BShe was curious: she had never seen a rabbit with a watch.
- CHer sister had told her to follow him.
- DShe was afraid he would be late for tea.
From the text
…when the Rabbit actually took a watch out of its waistcoat-pocket, and looked at it, and then hurried on, Alice started to her feet, for it flashed across her mind that she had never before seen a rabbit with either a waistcoat-pocket, or a watch to take out of it, and burning with curiosity, she ran across the field after it…
Bake-off
10 GB RTX 3080qwen3:8b @ 16kgemma3:12b @ 8k
VRAM resident7.85 GB, 100% GPU10.23 GB, 100% GPU
Spare headroom~2.7 GiB~0.5 GiB
Speed~100 tok/s~63 tok/s
Per quiz15 to 18 s26 to 30 s
A full chapter (~5,700 tokens)comfortablefits, barely
Two chaptersfitsdoes not fit
Also tested: mistral-nemo:12b, llama3.1:8b and llama3. Every model held the JSON schema; the choice came down to capacity.
Title only, no pages
Harry Potter and the Philosopher’s Stone, chapters 1 to 3ModelDefensible questions
qwen3:8b1 of 10
gemma3:12b3 of 10
Both passed every automated check.
How it works
- I capture a chapter. The input is a PDF, pasted text, or photos of the pages. Photos are read by tesseract.js in the browser, so the images never leave the device.
- The server refuses to guess. Without the source text it will not write a quiz, because a model asked for questions from a title alone invents them.
- Generation is queued per chapter. One job at a time goes to the local model through a provider interface, Ollama by default with Anthropic available behind the same shape.
- The output is checked in code. Choices are shuffled server side, and every answer is snapped to a supporting quote matched back against the captured text.
- The quiz is saved and shared. It lands in SQLite and I send a link to a phone, where the answers can be taken and reviewed.
Built with
- Ollama and qwen3:8bThe local model on an RTX 3080 at home, chosen by the bake-off for fit and speed.
- ExpressThe API, the generation queue and the static client in one process.
- SQLite and zodStorage for chapters and quizzes, and schema checks on every model response.
- tesseract.jsOCR for page photos, run in the browser.
- vitestTests for parsing, shuffling and grading.
- DockerOne image that runs the whole thing at home.
Decisions
- No text, no quiz. In my tests the model produced fluent questions from a title that were mostly wrong, and they still passed every format check. So the app asks for the pages and never asks the model what it remembers about a book.
- Fix bias in code, not in the prompt. Every model I tried favored some answer position: one run put seven of ten answers at index 1, another nine of ten at index 0. Shuffling the choices on the server works for whichever model comes next.
- Pick the model by measuring fit. On a 10 GB card a model is either fully in VRAM or too slow to use: gemma3:12b at 16k sat 77% on the GPU and lost two thirds of its throughput. The bake-off harness turned that into a measurement instead of a guess.