Master Local AI
  1. 0Load
  2. 1You type
  3. 2Tokenize
  4. 3To the GPU
  5. 4Prefill
  6. 5Decode
  7. 6Show text
Can it run?

Chapter 11 · Expert

Beyond Chat: RAG, Agents, Vision, Speech and Images

Retrieved text is prompt text: more cards, slower first token.

  1. 0Loadonce: file → VRAM
  2. 1You typetext in the app
  3. 2TokenizeCPU: text → IDs
  4. 3To the GPUIDs cross PCIe
  5. 4Prefillwhole prompt, one pass
  6. 5Decoderead all weights → 1 token
  7. 6Show textID → word · repeat 5

1Watch

Now

Press a step on the right, or change any control under the machine.

Readout

Key to the machine

Library desk: the arm fetches index cards into the tray. The reader reads the tray first. A fuller tray means a longer wait. Agents add a card per tool call.
How the numbers work

Retrieval-augmented generation as a library: documents are cut into chunks and filed as index cards by embedding (here nomic-embed-text, about 137 M parameters, 768-dim vectors). A question is stamped into a vector, the arm pulls the top-k cards by cosine similarity, and every retrieved token lengthens the prompt the chat model must read before its first token. In agent mode the reader prints a thin tool-call slip (about 120 tokens of JSON) into the tray; the arm takes it to the incline lift, the loop carries it to a calculator, a file drawer or a web terminal, and it comes back as a thick result card (call plus about 800 tokens of result, a page or file excerpt) that the arm files in the tray before the context is read again; with prefix caching only the new cards are read. The glass tube fills with green as the reader writes the KV cache; past the room left after the weights its outline turns red and cards spill onto the bench. Estimates from _model.js for Q4_K_M weights and an FP16 KV cache: decode from memory bandwidth, prefill ≈ 2 × active params × tokens ÷ (dense FP16 tensor rate × 40%), the rate taken as NVIDIA's AI TOPS (FP4, sparse) ÷ 8 — about 419 TFLOPS on a 5090, 4.3 on the M4. Attention's own cost grows with context and is left out, so long prompts are optimistic.

  • Cards = question, chunks, tool calls
  • Tray = the prompt
  • Green tube = KV cache
  • Out-basket = printed answers
Speed
Open full screen ↗

2Try this

Then change anything yourself.

3Read on ↓

Lab: a fully local RAG pipeline

You can run RAG fully on this machine, with the embedding model it already has. We embed the course notes with nomic-embed-text in Ollama. The vectors go in a NumPy matrix (the vector index). Retrieval uses cosine similarity. The model expects task prefixes (search_document: and search_query:) for best quality.

Lab · runs on your own machine

uv run labs/11_local_rag.pyDownload all labs (.zip)

Needs uv and, for the live parts, Ollama with a small chat model: ollama pull qwen3:0.6b.

Show the code
# /// script
# requires-python = ">=3.11"
# dependencies = ["numpy"]
# ///
import _local as local

import numpy as np

notes = [
    "Decode is memory-bandwidth bound: every token re-reads all active weights from VRAM.",
    "Prefill processes the whole prompt in one parallel pass and is compute bound; it sets time to first token.",
    "The KV cache stores keys and values per layer and token; grouped-query attention shrinks it.",
    "Q4_K_M is a GGUF k-quant of about 4.85 bits per weight and the usual quality/size sweet spot.",
    "NVFP4 uses E2M1 values in blocks of 16 with an FP8 scale and runs natively on Blackwell tensor cores.",
    "DGX Spark has 128 GB of unified LPDDR5X at 273 GB/s: huge capacity, modest bandwidth.",
    "The RTX 5090 has 32 GB of GDDR7 on a 512-bit bus at 1,792 GB/s.",
    "Continuous batching admits new requests at every decode step; PagedAttention stores KV in blocks.",
    "Partial offload to system RAM is slow because per-token time is the sum of the GPU and CPU reads.",
    "Mixture-of-experts models need memory for all experts but only read the active ones per token.",
    "Windows ML runs ONNX models and can use the TensorRT for RTX execution provider on NVIDIA GPUs.",
    "llama-server, Ollama, LM Studio and vLLM all expose an OpenAI-compatible chat completions API.",
]
emb_model = (local.embed_models() or [None])[0]
if emb_model:
    doc_vecs = np.array(local.embed(emb_model, [f"search_document: {n}" for n in notes]))
    doc_vecs /= np.linalg.norm(doc_vecs, axis=1, keepdims=True)

    def retrieve(question, k=3):
        q = np.array(local.embed(emb_model, [f"search_query: {question}"])[0]); q /= np.linalg.norm(q)
        scores = doc_vecs @ q
        top = np.argsort(-scores)[:k]
        return [(float(scores[i]), notes[i]) for i in top]

    for question in ["Why is my laptop slow when the model doesn't fit in VRAM?", "Which machine should run a 120B model?", "What format should I ship for Blackwell?"]:
        print("Q:", question)
        for score, text in retrieve(question):
            print(f"   {score:.3f}  {text}")
else:
    print("Run: ollama pull nomic-embed-text")

model = local.need_chat_model()
if model and emb_model:
    question = "Why is my laptop slow when the model doesn't fit in VRAM?"
    context = "\n".join(f"- {t}" for _, t in retrieve(question, 3))
    r = local.generate(model, f"Answer using only these notes:\n{context}\n\nQuestion: {question}\nAnswer:", num_predict=120, think=False)
    print(r["text"].strip())
    print(f"\nprompt {r['prompt_tokens']} tokens · prefill {r['prefill_s']*1000:.0f} ms · decode {r['decode_tps']:.0f} tok/s")
Sample output on this course's Mac (Apple M4, 32 GB)
Q: Why is my laptop slow when the model doesn't fit in VRAM?
   0.665  Partial offload to system RAM is slow because per-token time is the sum of the GPU and CPU reads.
   0.616  Decode is memory-bandwidth bound: every token re-reads all active weights from VRAM.
   0.593  Windows ML runs ONNX models and can use the TensorRT for RTX execution provider on NVIDIA GPUs.
Q: Which machine should run a 120B model?
   0.566  The RTX 5090 has 32 GB of GDDR7 on a 512-bit bus at 1,792 GB/s.
   0.562  NVFP4 uses E2M1 values in blocks of 16 with an FP8 scale and runs natively on Blackwell tensor cores.
   0.555  Windows ML runs ONNX models and can use the TensorRT for RTX execution provider on NVIDIA GPUs.
Q: What format should I ship for Blackwell?
   0.552  NVFP4 uses E2M1 values in blocks of 16 with an FP8 scale and runs natively on Blackwell tensor cores.
   0.539  Continuous batching admits new requests at every decode step; PagedAttention stores KV in blocks.
   0.505  llama-server, Ollama, LM Studio and vLLM all expose an OpenAI-compatible chat completions API.
Your laptop is slow because the model does not fit in VRAM, which limits the amount of data that can be processed at once. Here's why:

1. **VRAM Limitation**: If the model's size exceeds VRAM capacity, each token would require reading all active weights from VRAM, which is memory-intensive. This causes the system to slow down as it needs to perform operations like memory reads and compute.

2. **Partial Offload to System RAM is Slow**: Even if partial offloading is done, the time required for GPU and CPU reads still adds up, making the process

prompt 106 tokens · prefill 28 ms · decode 102 tok/s

Embeddings match wording, not reasoning. The question "which machine should run a 120B model?" usually misses the DGX Spark note, because the note never says "120B". Real pipelines add query rewriting, hybrid keyword and vector search, and a reranker (a small cross-encoder) over the top 20–50 hits.

The cost of retrieval

Retrieved text is prompt text. Each chunk adds c tokens of prefill, so k chunks add k\cdot c tokens. TTFT (time to first token) and KV memory grow with them (steps 1 and 2 above). Use better chunks, not more:

  • Use a reranker and a tighter k.
  • Keep a shared system prompt in the prefix cache.
  • Choose an embedding model small enough to share the GPU. nomic-embed-text has ~137M parameters, or ~270 MB at F16.

Agents

An agent is a loop of inference calls on a growing context. It decodes a plan or tool call, runs the tool, appends the result and prefills again. A 20-step task makes 20 or more inference calls. Four things decide whether it works locally:

  1. Tool-call reliability: the model must emit valid JSON in its trained format. Chat templates and grammar-constrained decoding (llama.cpp grammars, SGLang structured output) matter more than raw speed.
  2. Prefix caching stops the runtime from re-prefilling the system prompt, tool schemas and history at every step (steps 3 and 4).
  3. KV budget: the context grows at each step (Chapter 4's tanks; step 5).
  4. MCP (Model Context Protocol) gives local agents a standard way to reach tools and data.

Other modalities, other bottlenecks

Chat decode is bandwidth bound; almost everything else (prefill, VLM image tokens, diffusion, embeddings) is compute bound.

Workload Typical local model Bottleneck Notes
Vision-language Qwen2.5-VL, Gemma 3, Llama 3.2 Vision prefill: an image becomes hundreds to thousands of tokens the vision encoder runs first; resolution sets the token count
Speech-to-text Whisper, NVIDIA Parakeet encoder compute plus streaming latency small models; real-time factor is the metric
Text-to-speech Kokoro, Orpheus, VoxCPM autoregressive decode, latency to first audio streaming chunk size matters
Image generation FLUX, Stable Diffusion 3.5 compute bound: 4–50 denoising steps of a large network here tensor-core FLOPS and FP8/FP4 formats pay off directly
Embeddings / rerankers nomic-embed-text, bge, Qwen3-Embedding prefill-like batch compute cheap; batch them

This is why RTX tensor-core generations matter more for creative and multimodal apps than tokens/s charts suggest.

Explain it

  1. "A private document assistant on RTX workstations?" Use local embeddings, a vector store, a 14–32B instruct model at Q4/FP8 and a cached system prompt. Add citations, an RTX PRO 6000 for many users, and tests on your own questions.
  2. "Why do local agents run worse?" The tool-call format may mismatch, prefix caching may be off, and prefill of growing transcripts is slow. Runtime choice and configuration fix all of these.
  3. "Where do RTX GPUs shine beyond chat?" In compute-bound work (diffusion, video, speech, embeddings), where tensor-core speed and FP8/FP4 help directly.

Exercises

  1. Split any file into 400-token chunks, embed them, and test retrieval on five questions you write.
  2. Extend the RAG lab into a two-step agent with a calculator() tool, and count the prompt tokens per step.