av / dives /Local Models: Exercises
source about me

Bonus dive

Exercises: make the learning stick

Reading code teaches you less than predicting what it will do and then checking. This file turns each section of the README into a few quick active-recall prompts.

How to use it: work the section first, then come back. Commit to an answer before you run or reveal. The prediction is where the learning happens. Answers are hidden behind ▸ toggles.

The whole repo is free: there's no API key and no per-token bill anywhere. Section 2 needs no server at all; the rest just need a local runtime up.


Section 2: Will it fit? (offline)

Recall. Write the memory formula, and give bytes-per-parameter for fp16, q8, and q4.

▸ Answer

memory ≈ parameters × bytes-per-parameter (plus KV cache + overhead). fp16 ≈ 2.0, q8 ≈ 1.0, q4 ≈ 0.5 bytes per parameter. So a 7B model is ~14 GB at fp16 but ~3.5 GB of weights at q4, which is why q4 is the laptop default.

Predict, then run. Before running examples/01_quant_math.py: you bump an 8B model's context from 4k to 32k tokens. Roughly what happens to memory, and why?

▸ Answer

It grows a lot. The KV cache scales linearly with context length, and at 32k it can rival or exceed the weights themselves. The weights are fixed; the context is the lever. The example prints this curve.


Section 3: Your first local request

Recall. Compared to a hosted OpenAI call, what exactly is different in the code for a local call?

▸ Answer

Only the client's base_url (pointed at http://localhost:11434/v1), and the api_key, which is required by the SDK but ignored by the local server, so any string works. The messages, parameters, and response shape are identical. That's the entire repo in one sentence.

Predict. You run examples/02_first_local_request.py with no server running. What happens: a crash, a hang, or something else?

▸ Answer

Something else: providers.ensure_server() probes the port first, prints start-up instructions, and exits cleanly (code 0). No stack trace, no charge because there's nothing to charge. Graceful degradation is the local analogue of the sibling repos' "missing API key" guard.


Section 4: Picking a model

Recall. Two models: a 13B at q3 and a 7B at q6, and they use about the same memory. Which is usually the better pick, and why?

▸ Answer

Usually the 7B at q6. A higher-quality quant of a smaller model tends to beat a heavily-crushed quant of a bigger one. Below ~q4 quality drops off, and q3 of a 13B may be noticeably degraded. Rule of thumb: smaller model + q4/q6 over bigger model + q2/q3.


Section 5: Quantization tradeoff

Predict, then run. Going from q8 to q4 on the same model: what happens to file size, memory, generation speed, and quality?

▸ Answer

Size and memory roughly halve; generation often gets a bit faster (less data to move); quality drops a little. For most tasks q4 keeps most of the quality at a quarter of fp16's size, which is why it's the default. examples/04_... shows the table and (with a server) measures tokens/sec.


Section 6: Serving engines

Recall. You need a high-throughput endpoint serving many concurrent users on a GPU box. Which engine, and why not Ollama?

▸ Answer

vLLM, which is built for serving at scale (continuous batching, high throughput). Ollama is fantastic for one user on a laptop but isn't aimed at many-user GPU serving. Crucially, your code is identical either way; only the base_url changes.


Section 7: Performance

Recall. Name the two speeds of local inference and which one a longer prompt mainly affects.

▸ Answer

Time-to-first-token (TTFT, the model reading/prefilling your prompt) and generation speed (tokens/sec once it starts). A longer prompt mainly grows TTFT; generation speed barely changes. And the very first call after the model loads is always slowest.

Predict, then run. In examples/06_performance.py, why does the long-prompt run show a bigger pause before text, but stream at about the same speed once it starts?

▸ Answer

Because the extra time is prompt processing (reading all those tokens), which is separate from generation. More input → longer TTFT; the per-token generation rate is set by the model and hardware, not the prompt length.


Section 8: Embeddings (local)

Recall. What changes about your code to get embeddings from a local model instead of a hosted one, and what's the recurring cost?

▸ Answer

Just the model name (an embedding model like nomic-embed-text) on the same local endpoint. The recurring cost is zero: no per-token embedding bill. This is the core of local RAG.


Section 9: Structured output & tools

Predict. You ask a small local model for "ONLY JSON" and it returns the JSON wrapped in ```json fences. Is that a failure? What does the example do?

▸ Answer

Not a failure. Small models drift more than hosted ones. The example strips the fences before json.loads, the same defensive parsing you learned for hosted models. Lower temperature, a tighter prompt, or a stronger model (qwen2.5, llama3.1) all help reliability.


Section 10: Local vs. hosted

Recall. Give two things local wins on and two things hosted wins on.

▸ Answer

Local wins: privacy/data control, cost at volume, offline use, no rate limits. Hosted wins: peak answer quality (the biggest models don't fit locally), zero ops, elastic scale, day-one access to new models. The best design is often both using local for the common/private/high-volume path and hosted for the hard cases.


Section 11: Run the series locally

Recall. What three lines in a sibling repo's .env make it run against your local model, and which one is a no-op locally?

▸ Answer

OPENAI_BASE_URL=http://localhost:11434/v1, MODEL=<your local tag>, and OPENAI_API_KEY=local. The API key is the no-op: the local server ignores it, but the SDK insists on a non-empty string. No code changes; the prompt-engineering, RAG, agents, and evals repos all run locally for $0.


Capstone: local_chat.py

Do. Run python hands_on/local_chat.py --fit 8 (offline), then start a chat. What did --fit tell you, and what's the tokens/sec line after each reply measuring?

▸ Answer

--fit 8 printed an offline memory estimate for an 8B model (weights + KV + overhead) and whether it fits 16 GB, which is Section 2 without a server. The tokens/sec line is the generation speed (Section 7) of your machine on each reply, plus the time-to-first-token.

Stretch. Pull a second model (ollama pull qwen2.5) and chat with both via --model. Do you feel the size/speed/quality tradeoff from Sections 4–5? Then point a sibling repo at your local server (Section 11) and run its capstone for free the first time the RAG or agents dive runs with no key and no bill, "local is an ops choice, not a new API" has fully landed.


Where to take it next

Build something you'd actually keep: a fully-local note summarizer, a private RAG over your own files, or a coding helper, all $0 to run and with nothing leaving your machine. The moment you stop thinking about per-token cost while iterating, the point of local has clicked.