To train an AI model on your own data in the cloud, you turn your data into examples of the exact output you want, hold some back for testing, upload the rest, and fine-tune an open model on a cloud GPU with a spending limit. On Nodus that is one Volume upload and one TrainingJob, and a first LoRA run on a few thousand examples costs well under a dollar of GPU time. The GPU is the cheap part: most of the work, and most of the quality, is in preparing the data.
This guide walks through the whole path with a support-ticket example. Every command and manifest field is copied from the current Nodus docs, and the data-prep script below is one we ran ourselves.
What kind of data can you train a model on?
You can train on any data that shows the model the behavior you want, but its shape decides the training method. A model learns from examples, not from facts sitting in a folder. So the first question is what your data already looks like.
| Your data looks like | Method | Nodus runtime |
|---|---|---|
| Questions with the answers you want | Supervised fine-tuning | nodus/sft |
| Two answers per question, one marked better | Preference tuning |
nodus/dpo or nodus/orpo
|
| Single answers with a thumbs up or down | Preference tuning from labels | nodus/kto |
| Raw documents, manuals or code in your domain | Continued pretraining |
nodus/sft with task: Pretrain
|
| Tasks a program can grade as right or wrong | Reinforcement learning | nodus/grpo-lora |
Runtimes from the Nodus training runtimes reference, checked November 10, 2026.
Most teams start in the first row. Support tickets, chat logs, labeled forms and past reports all become question and answer pairs with a little cleanup.
If your data is mostly facts that change every week, such as prices or inventory, fine-tuning is the wrong tool. Retrieval keeps those facts current without retraining. Our RAG vs fine-tuning post has the decision rule.
How do you prepare your data for training?
Turn each record into one JSON line holding the input and the exact answer you want, then drop empty and duplicate rows. The nodus/sft runtime reads prompt and completion JSONL, and you map your own column names in the manifest, so you do not need to rename fields.
Here is the script we use for a CSV export, such as tickets from a helpdesk:
"""Turn a CSV of questions and answers into train/test JSONL for nodus/sft."""
import csv, hashlib, json, sys
src, question_col, answer_col = sys.argv[1], sys.argv[2], sys.argv[3]
rows, seen = [], set()
with open(src, newline="", encoding="utf-8") as f:
for r in csv.DictReader(f):
q, a = (r.get(question_col) or "").strip(), (r.get(answer_col) or "").strip()
if not q or not a or (q, a) in seen:
continue # drop empty rows and exact duplicates
seen.add((q, a))
rows.append({"question": q, "answer": a})
# Hold out 10% of distinct questions as a test set that never touches training.
def bucket(q):
return int(hashlib.sha256(q.lower().encode()).hexdigest(), 16) % 10
train = [r for r in rows if bucket(r["question"]) != 0]
test = [r for r in rows if bucket(r["question"]) == 0]
for name, part in (("chats.jsonl", train), ("test.jsonl", test)):
with open(name, "w", encoding="utf-8") as out:
for r in part:
out.write(json.dumps(r, ensure_ascii=False) + "\n")
print(f"kept {len(rows)} rows: {len(train)} train, {len(test)} test")
Run it as python prepare.py tickets.csv subject agent_reply. We tested it on a synthetic 6,200-row export with 64 blank answers and every question appearing twice. It kept 3,000 rows, wrote 2,723 to chats.jsonl and 277 to test.jsonl, and no question appeared in both files.
The test split works by hashing the question, not by picking random rows. That keeps every copy of a repeated question on the same side. If the same question lands in both training and test data, your scores look better than the model really is. Our post on splitting data with repeated prompts shows how that leak happens.
Read 50 rows by hand before you upload anything. The model copies whatever your answers have in common, including the typos, the signatures and the "let me check with the team" replies.
How do you get your data into the cloud?
Upload it to a Nodus Volume, which is named storage that keeps every change as a numbered revision. From a laptop, two commands do it:
nodus create volume support-chats --size 1Gi
nodus volume put support-chats ./chats.jsonl /chats.jsonl
Keep test.jsonl off the Volume until training is done. Your organization's first 10 GB of Volume storage is included, according to the Volumes guide, which is plenty for text datasets.
If your data already lives elsewhere, a Volume can import it once from Hugging Face, a git repository, an HTTPS URL or an S3 bucket. It can also run a query against a database you have connected. The Connections guide covers the bucket and database setup.
How do you run the training job?
Write a TrainingJob manifest that names a base model, your Volume and a spending cap, then price it with a dry run before anything starts. This one fine-tunes Qwen3 0.6B with LoRA, maps your question and answer columns, and asks Nodus to keep 10% of the rows for validation:
apiVersion: nodus.dev/v1beta1
kind: TrainingJob
metadata:
name: support-sft
spec:
runtime: nodus/sft
model:
uri: hf://Qwen/Qwen3-0.6B@c1899de289a04d12100db370d81485cdf75e47ca
data:
volume: support-chats
path: chats.jsonl
format: JSONL
columns: {prompt: question, completion: answer}
validationFraction: "0.1"
parameters:
steps: 600
evalSteps: 100
lora: {r: 16, alpha: 32}
validation:
patience: 3
maxCostUSD: "5.00"
nodus apply -f sft.yaml --dry-run -o yaml
nodus apply -f sft.yaml
The dry run compiles the Job it will create and prices it, with an estimated cost to completion and start time. It also rejects a misspelled parameter before you pay anything, and maxCostUSD caps what the run can spend.
With parameters.validation set, the runtime scores a checkpoint every 100 steps and exports the one with the lowest validation loss. If the kept checkpoint does not beat the starting model, the run fails its evaluation, and the weights stay downloadable. Training is in Beta on Nodus, so check the training guide for field changes before you copy this into a pipeline.
How much does it cost to train on your own data?
For a dataset this size, the GPU time costs cents. Here is the arithmetic for a 6,000-row export:
- Holding out 10% of questions for testing leaves about 5,400 rows on the Volume.
- Validation takes about 10% of those, leaving about 4,860 training rows.
- The
nodus/sftdefaults are 4 examples per step and 4 accumulation steps, so one optimizer step covers 16 examples. That makes about 304 steps per pass, sosteps: 600is about two passes.

Only about four in five of your rows train the model. The rest are how you find out whether it worked.
The runtime publishes the seconds per step its estimates use: 1.2 s on an RTX 4090, 1.3 s on an RTX A6000 and 1.0 s on an L40S. Those are the three GPUs its preset picks for a Qwen3 0.6B LoRA run. Multiplied by 600 steps and the price per hour:
| GPU | Listed on the Nodus pricing page | Time for 600 steps | GPU cost |
|---|---|---|---|
| RTX 4090 | $0.290/hr | 12 min | $0.06 |
| RTX A6000 | $0.333/hr | 13 min | $0.07 |
| L40S | $0.793/hr | 10 min | $0.13 |
Prices checked November 10, 2026 on the Nodus pricing page. Step times from the sft runtime reference. Estimates of GPU time only; validation passes, model caching and data preparation add a little.

At a few cents a run, you can afford to retrain after every round of data cleanup.
These step times are the runtime's generic figures, re-measured nightly, not a benchmark on your model. Longer examples and bigger models take longer per step. For an 8B model and larger datasets, our post on what training your own LLM takes prices each path.
The decision rule that falls out of this: spend your effort on data, not on GPUs. If a run costs cents, the expensive mistake is training on messy answers, not picking the slower card.
How do you know the model learned from your data?
Score the trained model and the base model on test.jsonl, the questions it never saw. Download the result and the adapter when the run finishes:
nodus get tj -w
nodus cp tj/support-sft:outputs/results.json ./results.json
nodus cp tj/support-sft:outputs/adapter/adapter_model.safetensors ./adapter_model.safetensors
results.json records the starting validation loss and the loss of the checkpoint it kept. Lower validation loss helps you pick a checkpoint, but it is not proof of better answers. Generate answers for every test question with both models and compare them side by side, the way your users would.
If the tuned model is not clearly better on test questions, go back to the data before you change any setting. Fix inconsistent answers, add examples for the questions it gets wrong, and rerun.
FAQ
How much data do you need to fine-tune a model?
Validation on Nodus needs at least 20 rows. Past that, consistency matters more than count, because the model learns whatever your answers have in common, mistakes included.
Is my data private when I train in the cloud?
On Nodus, your dataset sits in a Volume in your own project, and the training docs describe pinned, read-only inputs prepared for each run. Check the training guide for how inputs reach the trainer and which capacity a run may use.
Can I train on PDFs or Word documents?
Yes, after you extract the text. Raw documents suit continued pretraining with task: Pretrain, and Q&A pairs written from those documents suit fine-tuning.
Should I train a model from scratch on my data?
Almost never. Starting from an open model like Qwen3 or Llama keeps the language skills it already has. Using the method from our cost of training an LLM post, we put scratch training for an 8B model at about $19,000 of GPU time, against about $2 for a LoRA fine-tune.
Originally published on the Nodus blog.
Top comments (0)