Module 22 of 26 · Open Source Track
Cost
Optimization

"Free" open source models are not free — they burn hardware, electricity, and your users' patience. Six levers cut response time 3–10x without losing quality: cost awareness, model choice, quantization, lean prompts, batching, and caching.

Track 7: Production Engineering ⏱ ~19 min read 22 / 26
Concept 1 of 6

"Free" local models still cost you

There is no per-token invoice from a cloud provider — but a local model still consumes hardware, electricity, and time. The real equation is cost = inference_time × compute_per_sec + latency_impact.

On a laptop, compute-per-second is near zero but latency impact is huge: a 70B model on CPU can take 10 minutes per query, so your agent loop waits 10 minutes per step. On a rented GPU the compute cost is real money. You optimize for both time and dollars.

Concept 1 of 6

Baking your own bread

BEFORE: You run a bakery and stop buying wholesale bread. "Making our own is free!" you announce — no more per-loaf invoice from the supplier.

PAIN: But in-house baking needs a commercial oven, electricity 24/7, a baker's hours, flour storage, and cleaning. You are not paying per loaf — you are running a factory with real overhead that never appears on a single tidy bill.

MAPPING: Open source LLMs work the same way. "No per-token fee" hides the oven (GPU), the power bill (electricity), and the baker's time (latency your users wait). Per-unit cost can win at scale, but the overhead is very real — you must measure it.

Concept 1 of 6

Three cost dimensions to track

  1. Hardware costAmortized purchase price or hourly rental of the machine running the model.
  2. Electricity costA 300W GPU server for 8 hrs/day is roughly $0.07–$0.15/day at US rates.
  3. Latency impactSlower tokens/sec means longer agent loops, worse UX, fewer requests served per hour.
  4. Measure, then decideThe same model runs 3–6 tok/s on CPU vs 80–120 on a GPU — a 3–10x swing that changes everything.
Concept 1 of 6

Pseudocode

FUNCTION true_cost(model, tokens):
  tps      = measured_tokens_per_sec(model)
  seconds  = tokens / tps
  compute  = seconds * compute_cost_per_sec
  # latency hurts even when compute is free
  latency  = seconds * user_pain_factor

  RETURN compute + latency

# laptop CPU: compute~0, latency huge
# rented GPU: compute real, latency small
Concept 1 of 6

Misconceptions

"Local models are free, so cost is zero."
There is no token invoice, but hardware, power, and latency are real. On a laptop the biggest cost is the wall-clock time users spend waiting.
"Bigger hardware always fixes it."
Sometimes — CPU→GPU is a 3–10x jump. But right-sizing the model and prompt often beats buying more silicon, for zero extra dollars.

True cost = compute + latency impact. "No invoice" is not "no cost" — time is the hidden bill.

A one-line hardware aside: CPU is dev-only (3–6 tok/s); a $0.35/hr RTX 4090 hits 80–120 tok/s. Benchmark p50/p95 before you commit.

Concept 2 of 6

Right-size the model to the task

Model choice is a three-way balance: task complexity (how much reasoning does this really need?), model size (how much VRAM it eats), and latency budget (the max acceptable response time).

The most common waste in local deployments: running a 13B–70B model on a task a 7B handles at ~97% quality — burning 2–5x the compute for no measurable gain. Start at the balanced tier (mistral:7b) and only climb when quality actually demands it.

Concept 2 of 6

Renting a moving truck

BEFORE: You need to move a single armchair across town, so you book the biggest 26-foot moving truck on the lot — "bigger is safer," you figure.

PAIN: The giant truck guzzles fuel, is a nightmare to park, and crawls through traffic. The armchair rattles alone in a cavern of empty space. You paid triple and arrived later than a hatchback would have.

MAPPING: A 70B model on a classification task is that 26-foot truck. Match the vehicle to the cargo: a 2B model for routing, a 7B for the everyday agent loop, and the big rig only when you are genuinely hauling a house.

Concept 2 of 6

The model tier ladder

  1. Tier 1 — fastest / smallestphi3:mini, gemma2:2b (2–3 GB). Great for classification and routing.
  2. Tier 2 — balanced defaultmistral:7b, llama3.1:8b (4–5 GB). The recommended starting point for most agents.
  3. Tier 2B — reasoning specialistdeepseek-r1:7b (4 GB). Stronger multi-step reasoning at a similar size.
  4. Tier 3 — near GPT-4mixtral:8x7b, llama3.1:70b (26–40 GB). GPU required; reserve for the hardest tasks.
Tier 1
Tier 2 ★
Tier 3
Concept 2 of 6

Match model to task

IF classify / route (<1s budget):
  → phi3:mini or gemma2:2b

IF summarize / extract (<5s):
  → mistral:7b Q4

IF normal agent loop (<10s):
  → mistral:7b or llama3.1:8b

IF planning / code gen (<20s):
  → deepseek-r1:7b

IF complex reasoning (GPU ok):
  → mixtral:8x7b / llama3.1:70b

Default to Tier 2; climb only when a benchmark proves you need to.

Concept 2 of 6

Misconceptions

"The biggest model is always the safest choice."
A 7B often matches a 70B at ~97% quality on routine tasks while running 2–5x faster and fitting on a laptop. Big models are for genuinely hard reasoning.
"Pick the model by gut feel."
Run 20 real prompts, measure p50/p95 latency, and score quality with a judge model. Choose the fastest model that clears your quality floor.

Right-size the model: complexity vs size vs latency. Start at the balanced 7B tier.

Overspending on model size is the #1 waste in local inference — and it is free to fix.

Concept 3 of 6

Shrink the weights, keep the smarts

Quantization lowers the numeric precision of a model's weights — from 16-bit floats down to 8-bit or 4-bit integers. That shrinks the file and speeds up inference, at a small cost in quality.

The sweet spot is Q4_K_M: about 98% of F16 quality at ~30% of the memory and roughly 2x the speed. The goal is not maximum fidelity — it is the minimum fidelity your task actually requires.

Concept 3 of 6

JPEG for model weights

BEFORE: A photographer shoots RAW — 40 MB per photo, perfect in every detail. Flawless, but a 10,000-photo trip fills a 400 GB drive and loads painfully slowly.

PAIN: You want to post them on a site that loads in under a second. RAW is unusable; JPEG at 20% quality turns faces into muddy blur. The trick is the sweet spot — compression the eye can't detect, at a fraction of the size.

MAPPING: Quantization is JPEG for weights. F16 is the RAW file (perfect, huge). Q4_K_M is the 80%-quality JPEG that looks identical but loads instantly. The "K" and "M" mean the most sensitive layers are compressed more carefully, so quality holds.

Concept 3 of 6

The precision ladder

  1. Q2_K — extreme squeeze~85% quality, noticeable degradation. Only for severe memory limits.
  2. Q4_K_M — the sweet spot~98% quality, ~4 GB VRAM, 2x faster than F16. The default for nearly everything.
  3. Q6_K / Q8_0 — high fidelity~99%+ quality, more memory. Use for code gen or evaluation baselines.
  4. F16 — reference100% quality, ~14 GB. Fine-tuning and research only.
Q2_K
Q4_K_M ★
F16
Concept 3 of 6

Which quant to pull?

DEFAULT:
  → pull Q4_K_M   # ~98%, 2x speed

IF only 4 GB RAM total:
  → Q3_K_M

IF code gen / hard reasoning:
  → Q6_K

IF benchmarking a baseline:
  → Q8_0

IF fine-tuning / research:
  → F16 (reference precision)

Unless you have a specific reason to go higher, Q4_K_M wins.

Concept 3 of 6

Misconceptions

"Quantization ruins the model's quality."
Q4_K_M keeps ~98% of F16 quality — imperceptible on most tasks — because the most sensitive layers get extra precision. The loss is real but tiny.
"Always run the highest precision I can fit."
F16 is 2x slower and 3x the memory for ~2% more quality you usually can't measure. Spend that headroom on a bigger model or batch size instead.

Q4_K_M is the default: ~98% quality, ~30% memory, 2x speed. Fidelity you can't perceive is fidelity you don't need.

Go higher only for code gen, accuracy-critical tasks, or evaluation baselines.

Concept 4 of 6

Fewer tokens = faster steps

Tokens are the unit of work. In the cloud, cutting tokens saves money; locally it saves time — effectively the same thing. An agent that re-appends the full tool response every step compounds fast.

5 steps × 2,000-token context = 10,000 tokens processed. Trim each step to 500 tokens and you process 2,500 — a 4x throughput gain on identical hardware. Lean prompts are free speed.

Concept 4 of 6

Packing a carry-on

BEFORE: For a weekend trip you throw your entire wardrobe into a giant suitcase — "just in case I need it."

PAIN: Now you lug a 50-pound bag through every terminal, pay overweight fees, and wait at baggage claim while carry-on travelers walk straight out. Every extra item you'll never wear slows down the whole trip.

MAPPING: Every token you send is an item in the model's suitcase — it re-reads all of it, every step. Pack only what this step needs: trim tool output, cap the response, prune old turns. A light bag moves faster through every checkpoint.

Concept 4 of 6

Five token-reduction techniques

  1. Stable prefix cachingKeep the system prompt identical — the runtime reuses its cached state for free.
  2. Cap response lengthSet a per-step token budget (e.g. 256) so generation can't run away.
  3. Truncate tool resultsA 10 KB JSON blob becomes 500 chars of essentials before it hits context.
  4. Slide the windowKeep the system prompt + last N turns; drop the oldest to stop context bloat.
  5. Ask for JSON, not proseStructured output is 40–60% denser than sentences.
Concept 4 of 6

Pseudocode

FUNCTION lean_step(history, tool_out):
  # 3: shrink tool output first
  tool_out = truncate(tool_out, 500)
  history.append(tool_out)

  # 4: keep system + last N turns
  history = system + last_n(history, 6)

  # 1 stable prefix + 2 cap + 5 json
  RETURN model.generate(
    prompt   = history,
    max_out  = 256,
    format   = "json")
Concept 4 of 6

Misconceptions

"Token count only matters for cloud billing."
Locally, every token is compute time. A bloated context is a slow context — fewer tokens directly means faster responses.
"Changing my system prompt each call is harmless."
It throws away the prefix cache. Keep it byte-identical so the runtime reuses the cached KV state and reprocesses it for free.

Stack five levers: stable prefix, capped output, truncated tools, sliding window, JSON. Together they often deliver 3–4x throughput.

Lean prompts cost nothing and speed up every single step of the loop.

Concept 5 of 6

Run many requests at once

For async workloads — document pipelines, nightly enrichment, bulk entity resolution — you don't have to finish one inference before starting the next. The runtime queues concurrent requests against the loaded model.

Sending 4–8 requests at once gives near-linear throughput gains, up to the model's concurrency limit. Push too far and you get queue congestion and out-of-memory risk instead of speed.

Concept 5 of 6

Lanes at the grocery checkout

BEFORE: A store opens one checkout lane and forces every shopper through it, one at a time, no matter how long the line grows.

PAIN: The queue snakes to the back of the store. But open 100 lanes with only 8 cashiers and it's chaos — half-staffed registers, confused shoppers, nothing actually moving faster.

MAPPING: Serial inference is the single lane. Batching opens more lanes — but only as many as you have cashiers (GPU/CPU capacity). Four to eight lanes is the sweet spot; a hundred just jams the aisles and risks a memory meltdown.

Concept 5 of 6

Batching with a safe cap

  1. Fan out the workLaunch all prompts as async tasks instead of awaiting each in turn.
  2. Cap concurrencyA semaphore (or p-limit pool) holds active requests at 4–8, never more.
  3. Gather resultsCollect responses as they finish; each carries its own timing and error state.
  4. Watch the curvebatch=8 is ~6x serial; batch=16 adds little and risks OOM — diminishing returns.
serial
batch=8 ★
batch=16
Concept 5 of 6

Pseudocode

limit = semaphore(8)   # safe cap

FUNCTION one(prompt):
  WITH limit:            # wait for a slot
    RETURN model.generate(prompt, max_out=256)

FUNCTION batch(prompts):
  tasks = [ one(p) FOR p IN prompts ]
  RETURN await gather(tasks)

# 20 prompts, cap 8 -> ~6x vs serial
Concept 5 of 6

Misconceptions

"100 simultaneous requests = 100x throughput."
The runtime serializes work against one GPU/CPU. Past the sweet spot you get queue congestion and memory pressure, not speed.
"Batching helps interactive chat too."
Batching wins on async bulk jobs. A single user waiting on one reply gains nothing — there's only one request to run.

Fan out async work, but cap concurrency at 4–8. batch=8 is roughly 6x serial; beyond that, returns fade and OOM risk climbs.

Use a semaphore or p-limit pool so you never overwhelm the hardware.

Concept 6 of 6

The fastest inference is none

If you've answered a question before, don't pay to answer it again. A two-layer cache handles two cases: identical queries (exact-match on a hash key) and semantically equivalent ones (same meaning, different words).

Together they can eliminate 20–60% of inference calls in repetitive workloads — common in document pipelines and customer chatbots. A cache hit returns instantly; the model never runs.

Concept 6 of 6

A chef's prep station

BEFORE: Every time an order comes in, a cook chops onions and simmers stock from scratch — even for the dish that's been ordered forty times tonight.

PAIN: The kitchen falls hopelessly behind. Identical orders each trigger the same ten minutes of prep, and the tickets pile up while diners wait for work that was already done an hour ago.

MAPPING: A cache is the prep station holding ready components. Exact match = the identical dish, plated instantly. Semantic match = "the same thing, hold the salt" — close enough to reuse the base. Only genuinely new orders go to the stove.

Concept 6 of 6

Two layers plus TTL

  1. Layer 1: exact matchHash the query + model; a dict lookup returns instantly on identical repeats.
  2. Layer 2: semantic matchEmbed the query, compare cosine similarity to past ones; > 0.95 is a hit.
  3. TTL expiryEach entry has a time-to-live so stale answers age out (e.g. 1 hour).
  4. Fall back to inferenceOn a miss, run the model, then store the result in both layers.
query → exact? → semantic? → infer
Concept 6 of 6

Pseudocode

FUNCTION infer(query):
  # layer 1: exact match
  hit = exact_cache[hash(query)]
  IF hit AND NOT expired(hit):
    RETURN hit

  # layer 2: semantic match
  near = vector_search(query, top=1)
  IF near.similarity > 0.95:
    RETURN near.response

  # miss -> run model, then store
  out = model.generate(query)
  store(query, out, ttl=3600)
  RETURN out
Concept 6 of 6

Misconceptions

"Exact-match caching is enough."
Real users paraphrase and make typos. Without a semantic layer, "who owns this?" and "who is the owner?" miss the cache and both hit the model.
"Cache entries can live forever."
Data changes. Without a TTL you serve stale answers. Set an expiry so entries age out, and degrade gracefully to live inference on any cache error.

Two layers — exact-match + semantic — with TTL cut 20–60% of calls. The cheapest inference is the one you skip.

A semantic hit (cosine > 0.95) catches paraphrases the hash layer misses.

Test the core insight

One question per concept. Tap to reveal the answer.

Open the full module on desktop

The desktop version has you run a real model-selection benchmark (p50/p95 + a judge score), pull Q4_K_M variants, stack five prompt-efficiency techniques, build an async batch processor with a concurrency cap, and ship a two-layer AgentCache — in Python and Node.

Open M22 on Desktop → Costs · model selection · quantization · prompts · batching · caching · ~70 min

Module 22 of 26 · Open Source Track · Production Engineering