,

Fine-Tuning Azure OpenAI, from SFT to DPO and RFT (Azure Gen AI Series, Part 16)

SFT, DPO, and RFT on Azure OpenAI: which models take which method, what the training and hosting actually cost, and how to read the loss curve before you deploy.

Azure Gen AI Series · Part 16 of 30

Key takeaways

  • Fine-tuning on Azure OpenAI means one of three methods, SFT, DPO, or RFT, and it runs on LoRA under the hood, so a training job is cheaper and faster than a full weight update.
  • gpt-4.1, gpt-4.1-mini and gpt-4.1-nano take SFT and DPO. o4-mini takes RFT. Pick the method from the problem, not the model you happen to like.
  • Training is billed per token and capped per job. The number that surprises people is hosting, which bills by the hour whether or not anyone calls the model.
  • Start with about 50 clean examples on a mini model. Reach for DPO or RFT only after plain SFT stops improving.

A fine-tuned model you deployed and forgot about is not free while it sits idle. Azure bills the hosting of a fine-tune by the hour, from the moment the deployment exists, whether it serves a million requests that day or none. At the published rate of roughly 1.70 to 3.00 dollars an hour, one idle deployment runs about 1,200 to 2,200 dollars a month for answering nothing. [AUTHOR: add anecdote about an idle fine-tuned deployment that quietly ran up a bill]. So before any of the how-to: the training run is the cheap part, and the deployment is the meter you forget to read.

Who this is for: You can already call an Azure OpenAI model from code (Part 11) and you have tried prompting and retrieval (Part 12), and the model still misses your format, tone, or one narrow task. You want to know whether training weights is worth it and how to do it without a surprise bill. No ML background needed. If you have never deployed a model on Foundry, read Part 3 first.

What fine-tuning changes, and when to skip it

Fine-tuning trains a base model on your own labeled examples so its weights adapt to your task. In contrast to few-shot prompting, where you show a handful of examples inside every request, fine-tuning learns from far more examples than could ever fit in a prompt. Once the weights hold your pattern, you send fewer instructions per call, which cuts tokens and often trims latency. Azure does this with LoRA, low-rank adaptation, which trains a small subset of parameters instead of every weight. That is why a job finishes in hours, not days, and costs a fraction of a full retrain.

Here is the part most teams get wrong. Fine-tuning does not teach the model new facts reliably. If the gap is knowledge, last quarter numbers, an internal policy, a product catalog, that belongs in retrieval, not weights. Feeding facts through training bakes them in stale and gives you no citation trail. Fine-tuning earns its keep when the gap is behavior: a fixed output shape, a house tone, a classification the base model keeps fumbling, a format that prompting nudges but never nails. If prompt engineering gets you 90 percent of the way, try that harder first. Reach for training when the last stubborn 10 percent is consistency, not knowledge.

Where the gap lives decides the fixTry the cheaper layer first, move right only when it runs outPrompt + few shotFixes: wording,simple formatCost: near zeroRetrieval (RAG)Fixes: missingfacts, freshnessCost: index + callsFine-tuningFixes: tone, shape,narrow behaviorCost: train + hosting
Prompting and retrieval are cheaper and reversible. Fine-tuning is the layer you commit to when the problem is how the model behaves, not what it knows.

SFT, DPO, and RFT, and how they differ

Azure OpenAI gives you three customization methods, and which ones you can use depends on the model you pick. They are not interchangeable, and choosing wrong wastes a training run.

Supervised fine-tuning (SFT) trains on labeled input and output pairs. You give the prompt and the exact answer you want back. This is the workhorse and where almost everyone should start. It handles task specialization, output format, and tone.

Direct preference optimization (DPO) aligns the model to preferred answers. Instead of one correct output, you supply a chosen response and a rejected one for the same prompt. It adjusts weights toward what people prefer without training a separate reward model, so it is lighter and faster than classic RLHF. Reach for DPO when quality is a matter of taste you can rank but cannot pin down as a single right answer.

Reinforcement fine-tuning (RFT) optimizes against reward signals from graders that score each output, so it fits complex or multi-step behavior you can measure programmatically. On Azure it runs on o4-mini. It is the most involved of the three and the one to save for when SFT and DPO have both fallen short.

MethodWhat you provideOn which modelsReach for it when
SFTPrompt and the exact target answergpt-4.1, 4.1-mini, 4.1-nanoFormat, tone, task specialization. Start here.
DPOChosen vs rejected answer per promptgpt-4.1, 4.1-mini, 4.1-nanoPreference and style you can rank, not label exactly
RFTGrader that scores each outputo4-miniComplex behavior you can measure with a reward

One more note on availability worth checking before you plan: Microsoft has said GPT-5 reinforcement fine-tuning reached general availability, but access is gated and invitation only, so do not build a roadmap around it until your subscription actually has it. [VERIFY: GPT-5 RFT invitation gating, confirm current status]

Which models you can fine-tune right now

The fine-tunable set is smaller than the full model catalog, and the region list matters because a model you can train in one region is simply absent in another. As of July 2026 the current generation looks like this.

ModelStandard regionsGlobalDeveloperMethodsModality
gpt-4.1North Central US, Sweden CentralYesYesSFT, DPOText and vision to text
gpt-4.1-miniNorth Central US, Sweden CentralYesYesSFT, DPOText to text
gpt-4.1-nanoNorth Central US, Sweden CentralYesYesSFT, DPOText to text
o4-miniEast US2, Sweden CentralYesNoRFTText to text

A few things that trip people up. The gpt-4.1 family trains only in North Central US and Sweden Central, so if your Foundry resource lives in East US you either create a resource in a supported region or use Global training, which runs the job on capacity outside your region. Older gpt-4o and gpt-4o-mini remain fine-tunable, and if you need open weight models like Llama, Phi, or Mistral, those train through Azure Machine Learning managed compute rather than the Azure OpenAI path, which is its own workflow I cover in Part 5. The AWS equivalent of this decision, custom models on Bedrock versus SageMaker, follows the same logic and I walk it in the Bedrock fine-tuning part.

How your training file has to look

Training and validation data go in as JSON Lines, one JSON object per line, formatted as a chat conversation exactly like the Chat Completions API expects. Each line carries a messages array with system, user, and assistant turns. Two format rules bite people who skip the docs: the file must be UTF-8 encoded with a byte-order mark (BOM), and each file must be under 512 MB. A validation pass runs on upload and rejects anything that fails those checks.

On volume, a job will not start with fewer than 10 examples, but 10 changes nothing you would notice. Start with about 50 well-crafted examples, and treat hundreds to low thousands as the real target for a model you plan to ship. Doubling a clean dataset tends to lift quality roughly linearly, but a pile of low-quality examples drags the model below the base you started from, so prune before you train. For multi-turn data you can mark specific assistant turns with a weight of 0 to exclude them from the loss and 1 to keep them, which is handy when only the final answer in a conversation is the one you want the model to imitate.

From raw examples to a job that startsCollectraw pairsPrunekeep the bestSplittrain + validJSONLUTF-8 + BOM
The split step is easy to skip and the one you regret. Without held-out validation data you cannot see overfitting until production does.

Here is a small SFT file and a script that validates it before you spend a token, then submits the job.

# train.jsonl (one object per line, chat format)
{"messages": [{"role": "system", "content": "You are a terse support triage bot."}, {"role": "user", "content": "my invoice is wrong"}, {"role": "assistant", "content": "category: billing | priority: P2"}]}
{"messages": [{"role": "system", "content": "You are a terse support triage bot."}, {"role": "user", "content": "the app keeps crashing on launch"}, {"role": "assistant", "content": "category: bug | priority: P1"}]}
import json
from openai import AzureOpenAI

path = 'train.jsonl'
n = 0
# utf-8-sig reads through the BOM Azure needs on the file itself
with open(path, 'r', encoding='utf-8-sig') as f:
    for line in f:
        line = line.strip()
        if not line:
            continue
        row = json.loads(line)
        assert 'messages' in row, 'every line needs a messages key'
        n += 1

assert n >= 10, 'Azure rejects jobs with fewer than 10 examples'
print(f'{n} examples look valid')

client = AzureOpenAI(api_version='2025-04-01-preview')
up = client.files.create(file=open(path, 'rb'), purpose='fine-tune')
job = client.fine_tuning.jobs.create(
    training_file=up.id,
    model='gpt-4.1-mini-2025-04-14',
    method={'type': 'supervised'},
)
print(job.id, job.status)

Expected output: 512 examples look valid then a job id and a status of pending or queued. Failure mode: if you export the JSONL from a tool that writes plain UTF-8 with no BOM, the local script still passes but the Azure upload validation fails the job. Re-save with a BOM. API version shown is one recent preview and moves often. [VERIFY: current fine-tuning API version]

Standard, Global, or Developer training

When you submit a job you choose a training type, and it is a genuine tradeoff between data residency, price, and queue time. This is separate from where you later deploy the model for inference.

Standard trains inside your Foundry resource region and guarantees data residency. Use it when data must stay in a specific region for compliance.

Global is cheaper because it uses capacity beyond your region. Your data and the resulting weights are copied to wherever the job runs, and queue times are usually shorter. Use it when residency is not a hard rule and you want the lower price.

Developer, in preview, is the cheapest and runs on idle capacity. There is no latency or SLA guarantee and no residency guarantee, and a job can be preempted and resumed later. Use it for experiments and price-sensitive runs where you do not care when it finishes.

Gotcha

The BOM requirement is the single most common reason a first job fails. Editors and export scripts default to plain UTF-8. On Windows PowerShell, Set-Content -Encoding utf8BOM writes it correctly. In Python, open the file for writing with encoding='utf-8-sig'. Check this before you blame your data.

Hyperparameters worth touching

On the first run, leave the hyperparameters on their defaults. Azure sets each of them to minus one, which means it picks a value from your data. You only reach in after you have seen a loss curve and have a reason. Three knobs are worth knowing.

n_epochs is how many full passes over the data the run makes. Set to minus one it is chosen for you. If the model overfits, fewer epochs is the first lever.

learning_rate_multiplier scales the training learning rate. Microsoft suggests experimenting between 0.02 and 0.2. A smaller value helps avoid overfitting on small datasets.

batch_size defaults to about 0.2 percent of your training set when left at minus one, up to a maximum of 256. Larger batches suit larger datasets and update weights less often but with less noise. There is also a seed you can pass so the same data and settings reproduce the same run, which is the difference between a controlled experiment and guessing.

What a fine-tuning run actually costs

Two meters run here, and they are very different sizes. Training is billed per token of training data, roughly 1.50 to 25 dollars per million tokens depending on the model. A single job is capped at 5,000 dollars: when a run hits that ceiling it pauses and hands you a deployable checkpoint rather than billing past it. Hosting is the other meter, and it bills a flat 1.70 to 3.00 dollars per hour for a deployed fine-tune regardless of traffic, which lands around 1,224 to 2,160 dollars a month. Inference tokens on top are priced the same as the base model. [VERIFY: per-model training rates and current hosting range on the Azure OpenAI pricing page]

Worked example

Say you train gpt-4.1-mini on 500 examples that average 800 tokens each, for 3 epochs. That is 500 × 800 × 3 = 1,200,000 training tokens, about 1.2 million. At an assumed 8 dollars per million that run costs near 10 dollars. Now deploy the result and leave it up for a month: 730 hours × 1.70 dollars = about 1,241 dollars. The training you agonized over cost ten dollars. The deployment you forgot about cost more than a hundred times that. Delete idle deployments.

Training is cheap. Idle hosting is not.gpt-4.1-mini worked example, US dollars035070010501400~10 dollarsTraining run~1,241 dollarsHosting, one month
The training bar is the thin red line along the axis. That is the point. Budget for hosting, and turn deployments off when a project pauses.

Reading the metrics and picking a checkpoint

While a job runs you watch two numbers. full_valid_loss is the validation loss at the end of each epoch, and it should fall. full_valid_mean_token_accuracy is validation token accuracy, and it should rise. When training and validation curves separate, the training loss dropping while validation flattens or climbs, you are overfitting. A checkpoint is saved at the end of every epoch, and when a job finishes you can deploy any of the three most recent. The value of that is picking the checkpoint from just before the model started memorizing rather than the final one.

Validation loss by epochThe uptick at epoch 4 is where to stop00.40.81.21.61234best checkpoint
Loss falls through epoch 3 (0.81), then ticks back up at epoch 4 (0.86). Deploy the epoch 3 checkpoint, not the last one.
Disclaimer: Deploying a fine-tuned model into a production path changes what your users receive. Deploy to a test deployment first, run your own held-out evaluation against the base model, and only then move traffic. Keep the base model deployment live so you can roll back in one config change.

Deploying, then fine-tuning the fine-tune

When a job succeeds you deploy the model like any other, and you can turn on automatic deployment so a successful run deploys itself. Inference then bills at base-model token rates plus the hourly hosting fee. If results are close but not there, you do not start over. Continuous fine-tuning lets you pick your fine-tuned model as the base for a fresh job and train it further on new examples, which is how you fold in edge cases you only found in production. When you are done, delete the deployment and the model, and clean up the uploaded training files, so the hourly meter stops.

My take: before you commit to hosting a fine-tune every hour of the month, check whether distillation gives you most of the win for less. Training a small model on a large model outputs, covered next in Part 17, is often the cheaper route to a small fast model that behaves the way you want.

Start with SFT on 4.1-mini, reach for DPO later

Here is what I actually do. First run: SFT on gpt-4.1-mini, about 50 to a few hundred clean examples, Global training for the price, defaults on every hyperparameter, and a held-out validation split so the loss curve means something. That covers the large majority of real cases, format, tone, and narrow task behavior, at the lowest cost and the smallest model I can get away with. I only move up to gpt-4.1 when the mini genuinely cannot hold the task, and I only reach for DPO when the problem is preference I can rank rather than a target I can label. RFT on o4-mini is the last resort, for behavior I can score with a grader.

When not to do any of this: if the model is missing facts, that is retrieval, not training, and if you need one clever output shape, spend another hour on the prompt first. What to validate before you trust a fine-tune: run your own evaluation against the base model on data the training never saw, and price out a full month of hosting before you deploy, because that number, not the training run, is what shows up on the bill. Your next call is small: take 50 of your best examples, save them as JSONL with a BOM, and run one SFT job on 4.1-mini this week.

Azure Gen AI Series · Part 16 of 30
« Previous: Part 15  |  Guide  |  Next: Part 17 »

References

About The Author


Discover more from Journal of Intelligent Infrastructure – By Dr Pranay Jha

Subscribe to get the latest posts sent to your email.

Leave a Reply

Your email address will not be published. Required fields are marked *

Architect’s Toolkit

About the Author

Dr. Pranay Jha is a Cloud and AI Consultant with 18+ years of experience in hybrid cloud, virtualization, and enterprise infrastructure transformation. He specializes in VMware technologies, multi-cloud strategy, and Generative AI solutions. He holds a PhD in Computer Applications with research focused on Cloud and AI, has published multiple research papers, and has been a VMware vExpert since 2016 and a VMUG Community Leader.

Discover more from Journal of Intelligent Infrastructure - By Dr Pranay Jha

Subscribe now to keep reading and get access to the full archive.

Continue reading