Every language model you call sits somewhere in a three-stage lifecycle: pretraining, optional fine-tuning, and inference. Mixing those stages up causes the classic mistake of "we'll just retrain GPT on our PDFs this sprint" — or the opposite mistake of fine-tuning when a better prompt and retrieval would have been enough.
What are the three stages?
| Stage | Plain-English idea | Who usually runs it |
|---|---|---|
| Pretraining | Teach general language skill from huge, broad data | Labs / cloud providers |
| Fine-tuning | Adapt an existing model to your tone, schema, or domain | Product teams / specialists |
| Inference | Use the fixed model to answer live user requests | Everyone shipping a product |
Why does the order matter? Pretraining is expensive and rare for most teams. Fine-tuning needs curated examples. Inference is what you pay for on every API call.
Prompting sits at inference time: you change the input, not the weights. Fine-tuning changes the weights (or adapters). Retrieval (RAG — retrieval-augmented generation) changes the context you feed at inference.
| Stage | Data scale | Compute | Changes weights? |
|---|---|---|---|
| Pretraining | Billions–trillions of tokens | Enormous (clusters, weeks–months) | Yes (from random init) |
| Fine-tuning | Hundreds–millions of examples | GPU-hours to days | Yes (full or adapters) |
| Inference | Live user inputs | Per-request GPU/CPU | No |
Those three scoreboards disagree. A model with great pretraining loss can still fail your support rubric; a fine-tune that nails the rubric can still be too slow in production.
Prefer prompting (+ tools/RAG) when:
Consider fine-tuning when:
A practical sequence most teams should follow: (1) strong base model + prompt, (2) add retrieval/tools, (3) measure, (4) fine-tune only if the remaining errors are systematic and data-backed.
A toy metaphor: a "pretrained" scoring table encodes general word preferences. Fine-tuning overrides a few weights for a domain. Inference reads the final table — prompts only change which keys you look up, not the table itself.
# "Pretrained" general preferences (higher = more likely)
pretrained = {
("hello", "world"): 2.0,
("hello", "there"): 1.5,
("error", "please"): 0.2,
("error", "traceback"): 1.8,
}
# Fine-tune on support-desk style: boost polite continuations
finetune_deltas = {
("error", "please"): +2.5, # domain override
("hello", "there"): +0.5,
}
def merge(base: dict, deltas: dict) -> dict:
out = dict(base)
for k, d in deltas.items():
out[k] = out.get(k, 0.0) + d
return out
adapted = merge(pretrained, finetune_deltas)
def infer(prev: str, weights: dict, prompt_bias: dict | None = None) -> str:
"""Greedy next-word from bigram-like weights; prompt_bias is inference-only."""
prompt_bias = prompt_bias or {}
candidates = {w: s for (p, w), s in weights.items() if p == prev}
for w, b in prompt_bias.items():
candidates[w] = candidates.get(w, 0.0) + b
return max(candidates, key=candidates.get)
print("base:", infer("error", pretrained))
# base: traceback
print("fine-tuned:", infer("error", adapted))
# fine-tuned: please
# Prompting without fine-tune: temporary bias at inference
print(
"prompted base:",
infer("error", pretrained, prompt_bias={"please": 3.0}),
)
# prompted base: please
Notice three different levers:
Real systems replace this dict with billions of neural parameters, but the lifecycle roles stay the same.
Pretraining builds general weights, fine-tuning adapts them to a domain or format, and inference (optionally with prompts and retrieval) is where those fixed weights serve live users.