4. Fine-tuning with an API¶
Intermediate · 12 min read
The fastest way to fine-tune is to let a provider do it: upload your JSONL, start a job, and get back a model name you call like any other. No GPUs, no training code. The trade-offs: you can only fine-tune the models the provider offers, the model stays on their platform, and you pay their training and inference prices.
This page uses the OpenAI fine-tuning API (the most widely used) with the dataset from page 2. Other managed options are in 4.6.
Which models can be fine-tuned changes
Providers add and retire fine-tunable models regularly. Check the provider's fine-tuning docs for the current list and prices before you start; the model name below is an example.
4.1 Estimate the cost first¶
Training is billed per token processed: every token in your file, once per epoch.
def training_cost_usd(examples: int, avg_tokens: int, epochs: int, price_per_million: float) -> float:
return examples * avg_tokens * epochs / 1e6 * price_per_million
# 800 examples x ~350 tokens, 3 epochs; example price $3 per 1M training tokens
print(f"${training_cost_usd(800, 350, 3, 3.00):.2f}")
# a much bigger distillation set
print(f"${training_cost_usd(20_000, 900, 2, 3.00):.2f}")
Training a small task model is usually cheap. The bigger costs are building the data and the inference price: fine-tuned models often cost more per token than the same base model, which is worth checking against the prompt tokens you save.
4.2 Upload the files¶
# no-run — needs OPENAI_API_KEY; pip install openai
from openai import OpenAI
client = OpenAI()
train_file = client.files.create(file=open("train.jsonl", "rb"), purpose="fine-tune")
valid_file = client.files.create(file=open("valid.jsonl", "rb"), purpose="fine-tune")
print(train_file.id, valid_file.id) # file-abc123..., file-def456...
Run your own validator (page 2) first. The API checks the format, but not your business rules.
4.3 Start a supervised fine-tuning job¶
# no-run
job = client.fine_tuning.jobs.create(
model="gpt-4o-mini-2024-07-18", # an example fine-tunable base model
training_file=train_file.id,
validation_file=valid_file.id,
suffix="shopkart-support", # appears in the new model's name
seed=42,
method={
"type": "supervised",
"supervised": {"hyperparameters": {"n_epochs": 3}}, # or "auto"
},
)
print(job.id, job.status) # ftjob-..., validating_files
Leave the other hyperparameters (batch_size, learning_rate_multiplier) on "auto" for the first run. Change one
thing at a time afterwards, guided by the validation loss and your evals.
4.4 Monitor it¶
# no-run
import time
while True:
job = client.fine_tuning.jobs.retrieve(job.id)
print(job.status, job.trained_tokens)
if job.status in ("succeeded", "failed", "cancelled"):
break
time.sleep(60)
for event in client.fine_tuning.jobs.list_events(job.id, limit=10):
print(event.created_at, event.message) # step N: training loss=..., validation loss=...
print(job.fine_tuned_model) # ft:gpt-4o-mini-2024-07-18:your-org:shopkart-support:AbC123
Jobs take minutes to hours depending on size and queue. The dashboard shows the training and validation loss
curves; watch for the overfitting pattern from page 3.
Intermediate checkpoints (client.fine_tuning.jobs.checkpoints.list(job.id)) are usable models too: if the last
epoch overfit, an earlier checkpoint may evaluate better.
4.5 Use the fine-tuned model¶
It's just a model name. Use the same system prompt as in training:
# no-run
reply = client.chat.completions.create(
model=job.fine_tuned_model,
messages=[
{"role": "system", "content": "You are ShopKart's support assistant."},
{"role": "user", "content": "parcel says delivered but nothing came!!"},
],
temperature=0,
)
print(reply.choices[0].message.content)
Then evaluate it against the base model with a good prompt, on your test set (page 6), before switching any traffic.
Preference tuning with DPO¶
When you can say which of two answers is better more easily than you can write the perfect answer, use Direct Preference Optimization. Each row has a prompt, a preferred reply and a worse one:
{"input": {"messages": [{"role": "user", "content": "I want a refund NOW"}]},
"preferred_output": [{"role": "assistant", "content": "[refund]\nI understand, and I'm sorry. Could you share the order ID? I'll start a refund review right away."}],
"non_preferred_output": [{"role": "assistant", "content": "[refund]\nRefunds take 5-7 days. Please be patient."}]}
# no-run
job = client.fine_tuning.jobs.create(
model=sft_model_name, # commonly: DPO on top of your SFT model
training_file=dpo_file.id,
method={"type": "dpo", "dpo": {"hyperparameters": {"beta": 0.1}}},
)
beta controls how far the model may move from the starting model: higher keeps it closer. Preference pairs are
also easy to collect from production: thumbs up/down, or an agent's edits to a drafted reply.
4.6 Other managed options¶
| Option | Models | Notes |
|---|---|---|
| OpenAI | Selected GPT models | SFT, DPO and reinforcement fine-tuning; model stays on OpenAI |
| Google Vertex AI | Gemini and open models | Supervised tuning, integrated with Google Cloud |
| Amazon Bedrock | Selected Amazon, Meta and partner models | Custom models inside your AWS account |
| Azure AI Foundry | Selected OpenAI and open models | Enterprise controls on Azure |
| Together AI, Fireworks, Hugging Face | Open models (Llama, Qwen, Gemma, Mistral…) | LoRA fine-tuning as a service; often you can download the adapter and serve it anywhere |
If you need to own the weights, run on-premise, or fine-tune a model no provider offers, train it yourself: next page.
Interview questions¶
Walk me through fine-tuning a model with the OpenAI API.
Prepare and validate chat-format JSONL with train and validation splits; upload them with files.create(...,
purpose="fine-tune"); create a job with fine_tuning.jobs.create specifying the base model, files and method
(supervised, with epochs); monitor status, events and loss; then call the returned ft:... model name with the
same system prompt used in training, and evaluate it against a well-prompted base model on a held-out test set.
How is fine-tuning priced?
Training is billed per token processed (dataset tokens × epochs). Inference on the fine-tuned model is billed per token, often at a higher rate than the base model. Total cost of ownership also includes data preparation, evaluation runs and retraining when the base model is retired.
What's DPO and when would you use it?
Direct Preference Optimization trains on pairs of a preferred and a non-preferred response to the same prompt, pushing the model toward the preferred style without a separate reward model. Use it after SFT when quality differences are easier to judge than to write, such as tone, helpfulness or conciseness.
Managed fine-tuning or training it yourself?
Managed: fastest, no infrastructure, but limited to offered models, data goes to the provider, and you're tied to their inference. Self-hosted: full control of open models and weights, on-premise options, any technique, but you need GPUs, ML engineering and serving infrastructure.
Practice¶
- Estimate the training cost of your own dataset with the calculator in 4.1.
- Write five DPO preference pairs for a task you know well.
- Find the current list of fine-tunable models and prices for one provider and note what changed recently.
Next: 5. LoRA fine-tuning an open model Back to: Fine-Tuning overview · Notes overview