DEV Community

Vibhu for Cairn

Posted on

A governed fine-tune for $0.86: Qwen3-4B on IFC/BIM, with a person approving every step

Fine-tuning a model is easy to start. Running it responsibly in a team is the hard part. Usually it's a script on a GPU box: nobody approves the spend, nobody records which data went in, and "is it better than the base model?" is a guess.

We ran a real fine-tune end to end with cairn, the open-source control plane we're building for exactly this, using only public packages from PyPI. Here is what happened, with the real numbers.

The run in numbers

What Value
Model Qwen3-4B-Base, 4-bit QLoRA (r 16)
Data 5,646 training rows of IFC/BIM instructions (from 42,680)
Hardware one NVIDIA T4 (AWS g4dn.xlarge)
Training 353 steps, 73 minutes
Cost $0.86 settled, against a $16.50 worst-case estimate approved up front
Test loss (348 held-out rows) 1.955 → 0.218 (89% lower)
General ability no measurable change on ARC, HellaSwag, WinoGrande, PIQA or MMLU

The adapter, the dataset, the training curve and the raw eval results are public: cairndev/qwen3-4b-ifc-bim-qlora and cairndev/ifc-bim-alpaca-dedup.

Two machines, one rule

A small control box runs the cairn engine. It holds the run record, the approval gates and the budget, and it starts and stops the GPU box. The GPU box runs only the trainer. Nothing listens on it: the engine reaches it over SSH. Datasets, checkpoints and the adapter move between the two through S3.

1. A person approves the data, and the data gets checked

dataset-prep/prepare paused before reading anything, and showed the source, its pinned Hub commit and the split plan. Only after approval did it download, check and split the data.

The checks found something worth knowing. 85% of the source rows were near-duplicates of each other: the dataset is template-generated, so the same IFC statement appears with only the numbers changed. Near-duplicate removal (MinHash, 0.8) kept 6,295 rows. The split is by template family, so variants of one template never sit on both sides of train and test, and the split's leakage check passed.

That number changed how we read every result after it. Without the check, we'd have trained on 42,680 rows and believed it.

2. The launch refuses before it spends

At launch, before any record existed, cairn checked:

  • Data roles: each dataset version's role, so a test set can't be passed as training data.
  • Holdout: the training rows don't overlap the validation or test rows, exactly or near.
  • The GPU estimate: priced from the box's hourly rate × each leg's timeout × its retries, $16.50 worst case, which fit the $25 budget.

Then it stopped at approve-run, with that estimate and the training settings on the card. The GPU box was only started after the approval.

3. The box starts, trains, and stops itself

After approval:

  1. compute/acquire tagged the instance with a lease, then started it.
  2. A 5-step smoke run proved the whole path on the box: data from S3, the tokenizer, 4-bit loading, a checkpoint back to S3.
  3. The real training ran 353 steps. Every 50 steps a checkpoint was mirrored to S3, so a killed process resumes instead of restarting.
  4. compute/release stopped the box right after training, before anyone looked at the results, so it didn't bill while we decided.

Each step settled its actual box time: smoke $0.05, train $0.81. The estimate is the ceiling you approve; the record keeps what you paid.

Train and eval loss over 353 steps on a Tesla T4: eval loss falls from 0.83 to 0.55 and flattens

4. Better than the base model, on data it never saw?

The pipeline's improved gate compares the candidate with the base model on held-out data, and it passed. We then measured both models on the 348-row test split, which never reached the trainer: loss fell from 1.955 to 0.218.

A lower loss on your own domain doesn't tell you whether the model got worse at everything else. So we ran a forgetting check with lm-evaluation-harness, with identical settings for both models:

Task Base Fine-tuned
ARC-Easy 0.767 0.760
ARC-Challenge 0.527 0.553
HellaSwag 0.647 0.653
WinoGrande 0.740 0.707
PIQA 0.800 0.787
MMLU 0.768 0.797

No change is larger than the noise at these sample sizes (about ±3–4 points). So: no measurable loss of general ability, which is not the same as none. The samples were small, and we say so on the model card.

5. A person promotes

The run then paused at promote-model, with the losses on the card. Promotion is a decision, not a side effect. Moving the tag needed its own approval too.

What it took

Every step was a cairn CLI command against the engine: install, connect the GPU box, prepare the data, launch, approve, read the findings, promote. The full list, in order, is in the new guide: Fine-tune a model on a GPU box. Swap in your own dataset (Alpaca, prompt/completion or chat) and base model; the pipeline is the same.

cairn is Apache-2.0 and self-hosted. If you fine-tune models in a team, try the guide on your own data, and tell us where it breaks: github.com/cairndev-sh/cairn.

Originally published on the cairn blog.

Top comments (2)

Collapse
 
tokenlat profile image
TokenLat •

The "person approves every step" model is honest about blast radius, but the real bottleneck is usually approval latency, not cost. Two things make governance sustainable:

1) An automated budget gate that rejects any run over X tokens before it starts — the human reviews the plan, not the bill.

2) Cheap open-weight models: a full fine-tune here runs $0.86 instead of $200, so you can afford to experiment with governance on.

The $0.86 is the tell — at that price the human-approval overhead can exceed the compute, so the gate has to be cheap and automatic while human sign-off stays reserved for the high-blast-radius calls. Do you gate on total token budget, or just the steps that need a human?

Collapse
 
alexshev profile image
Alex Shev •

Human approval is most useful when the approver sees the proposed change, its evidence, and its blast radius in one place. Recording the approval decision alongside the dataset and configuration versions would make the governance claim auditable later.