MLOps & LLMOps / Operating Models
What changes when the model is an LLM.
Reviewed by Yuvaraj
Classic MLOps grew up around a model you train and own: the discipline centers on data pipelines, training runs, and the versioned weights you ship. LLMOps often inverts that picture, you consume a frontier model through an API, so you no longer control the weights, the training data, or when the model changes underneath you. What you do control collapses onto three things: the prompts you send, the context you assemble around them, and the evaluation that tells you whether the whole system still works. This lesson covers the operational machinery that shift demands, and because tooling here turns over quickly, treat specific products as replaceable and the underlying principles as durable.
In classic MLOps the artifact you version, deploy, and monitor is a model file, the trained weights, produced by a pipeline you own end to end. When you consume an LLM over an API, that artifact belongs to the provider. The levers left in your hands are the prompt template, the context you build (system messages, few-shot examples, retrieved documents), the decoding parameters, and the guardrails wrapped around the call. So the thing you actually deploy is a prompt-plus-config bundle, not a .safetensors file, and every LLMOps practice below follows from that inversion.
Once a prompt template drives production behavior, it is code, not a throwaway string buried in a function. It needs version control, review, a changelog, and instant rollback. A prompt artifact bundles more than text: the model id, decoding parameters, stop sequences, tool schemas, and the eval set it was validated against.
# prompts/support-triage.v8.yaml
id: support-triage
version: 8
model: llm-large-2025-06-15 # a pinned dated snapshot, never a floating "latest" alias
params:
temperature: 0.2
max_tokens: 512
stop: ["</reply>"]
system: |
You are a support-triage assistant. Classify the ticket and draft a reply.
Return valid JSON matching the schema. Never invent account details.
template: |
Ticket: {{ticket_text}}
Customer tier: {{tier}}
eval_set: golden/support-triage.v3.jsonl
Store these in a registry (files in git, or a dedicated prompt store), give each change an immutable version id, and decouple prompt deploys from full app releases so you can iterate and A/B test without redeploying the service. Critically, record so a later regression can be traced to its source.
Ask about this lesson, or about anything in AI. Answers cite the lessons they draw on.
Finished this lesson?
Mark it complete to earn XP, keep your streak, and schedule a review.
A classic model with fixed weights and a fixed input produces a fixed output, so tests can assert exact equality. LLMs sample. Temperature rescales the logits before the softmax,
so higher flattens the distribution and increases output variance. Even at (greedy argmax) you are not guaranteed byte-identical output across calls: floating-point non-associativity in batched GPU kernels, load-dependent batch composition, mixture-of-experts routing, and silent provider-side updates all introduce drift.
Regression testing therefore moves from exact match to property and distribution checks: assert that output is valid JSON against a schema, contains a required fact, or passes a rubric, and run each case times, tracking a pass rate rather than a single result. A test that expects one canonical string will be flaky by construction.
Because outputs are free text, there is rarely a single accuracy number to report. Instead you maintain a golden set, a curated collection of representative inputs paired with expected answers or scoring rubrics, including edge cases and past failures, and score it along several axes: correctness, faithfulness/groundedness, format validity, safety, and tone.
Scoring uses whatever is cheapest and most reliable per axis: programmatic checks where possible (schema validation, regex, does the code compile), and LLM-as-judge for open-ended quality, where a strong model grades an output against a rubric or compares two outputs pairwise. Judges are scalable but biased, position bias, verbosity bias, and self-preference are well documented, so calibrate the judge against human labels before you trust its scores as a promotion gate.
Classic inference cost is roughly fixed per prediction; LLM cost is variable and dominated by token counts. Per request,
where are input and output tokens and are the per-token prices for the chosen model. Cost balloons quietly through longer retrieved context, retries, and agent loops that call the model many times per task. Log tokens, cost, model id, prompt version, and latency, including time-to-first-token for streaming, on every request, and monitor p50/p95/p99 latency and cost per feature and per user against budgets.
Caching is one of the biggest cost and latency wins, and one of the easiest to get subtly wrong.
A classifier can only emit a label; an LLM can emit anything, PII, toxic text, an injected instruction it was tricked into following, a hallucinated fact, or malformed output. Guardrails run inline as part of serving: input filters (prompt-injection detection, PII redaction), output filters (moderation, schema validation, groundedness checks against retrieved sources), and defined fallback behavior when a check fails. These filters add latency and cost, are often themselves model calls, and must be versioned and monitored for their own false-positive and false-negative rates.
The weights are not yours, so the provider can update or deprecate a model and shift behavior, output style, format adherence, latency, and price, without you changing a line. Defend against silent drift: pin explicit dated snapshots rather than floating aliases like latest; treat a model bump like a dependency upgrade by re-running the full eval suite and canarying before rollout; watch deprecation notices; and keep a provider-abstraction layer so you can migrate models without rewriting the app.
You run a support-triage feature that classifies a ticket and drafts a reply. You want to edit one line of the system prompt. Here is everything that must be versioned and re-evaluated before it reaches users.
Changing the model version runs the same pipeline, but the blast radius is larger: the model id is part of the artifact, so you re-baseline the entire eval suite rather than one case, re-check decoding parameters, re-price cost per token, re-validate every guardrail, and invalidate any cache entries keyed on the old model-plus-prompt pair.
The concrete checklist of what to version and what to log follows the same split:
| Layer | Version / track | Why it matters |
|---|---|---|
| Prompt template | Immutable version id, diff, author | A prompt is a deployable artifact; roll back on regression |
| Model | Pinned dated snapshot id | A silent provider update can shift behavior, cost, and latency |
| Decoding params | temperature, top-p, max tokens, stop | They change output variance and cost |
| Context assembly | Retrieval config, few-shot set, system message | Determines what the model actually sees |
| Eval suite | Golden-set version and scores | The regression gate before promotion |
| Guardrails | Input/output filter version | Part of the serving path; affects safety and latency |
| Per request | Prompt + model id, tokens, cost, latency, cache hit, guardrail verdict | Attribution, budget alerts, and drift detection |
Common mistakes
latest instead of a dated snapshot, so provider updates change behavior under you with no eval run.