Track B β Managed Fine-Tuning on Azure AI Foundry
Lesson 7: Track B β Managed Fine-Tuning on Azure AI Foundry
In Track A you owned every moving part. Track B is the opposite trade: you supply a JSONL file and a model name, and Microsoft handles GPUs, checkpoints, safety evaluation and serving. You give up control and gain a deployment that behaves exactly like the base models you already call.
Under the hood it is still LoRA. The Azure documentation is explicit that they use low-rank adaptation to fine-tune models "in a way that reduces their complexity without significantly affecting their performance," approximating the original high-rank matrix with a lower-rank one. Same mathematics as Lesson 3 β you just never see the adapter.
What Azure AI Foundry actually offers
Three fine-tuning methods, and not every model supports every method. This table is the single most useful thing to check before you start:
| Model | Methods | Modality | Regions / Data Zones |
|---|---|---|---|
| gpt-4o-mini (2024-07-18) | SFT | Text β text | North Central US, Sweden Central / US |
| gpt-4o (2024-08-06) | SFT, DPO | Text + vision β text | East US2, North Central US, Sweden Central / US |
| gpt-4.1 (2025-04-14) | SFT, DPO | Text + vision β text | North Central US, Sweden Central / US, EU, Asia |
| gpt-4.1-mini (2025-04-14) | SFT, DPO | Text β text | North Central US, Sweden Central / US |
| gpt-4.1-nano (2025-04-14) | SFT, DPO | Text β text | North Central US, Sweden Central / US |
| o4-mini (2025-04-16) | RFT | Text β text | East US2, Sweden Central / US |
| gpt-5 (2025-08-07) | RFT | Text β text | North Central US, Sweden Central / US |
| Ministral-3B, Qwen-32B, Llama-3.3-70B-Instruct | SFT | Text β text | US data zone |
Note also the two distinct model populations: Microsoft's proprietary models (the GPT family β frontier quality, closed weights, endpoint-only) and open-weight models (Qwen-32B, Llama-3.3-70B, Ministral-3B). The open-weight ones are dramatically cheaper to host β as Lesson 8 shows, $0.30β0.33/hour against $1.70/hour.
The data format β and the two requirements people miss
Azure consumes the same conversational JSONL you would feed Unsloth. It must be formatted as JSON Lines in the conversational format the Chat Completions API uses. There are two hard requirements that catch people out:
open(path, "w", encoding="utf-8-sig") β the -sig suffix is what adds the BOM.
{"messages": [{"role": "system", "content": "You are a claims adjudicator. Respond only with valid JSON."}, {"role": "user", "content": "Claim: rear-ended at a stoplight, police report filed."}, {"role": "assistant", "content": "{\"decision\":\"approve\",\"payout_usd\":4200}"}]}
Three capabilities worth knowing about:
| Feature | How it works |
|---|---|
| Multi-turn conversations | Put the whole exchange in one line's messages array. Supported directly. |
| Per-message weighting | Add "weight": 0 or "weight": 1 to an assistant message to exclude it from training. This is how you keep multi-turn context without training on a weak intermediate answer. |
| Vision fine-tuning | Use the content array with typed parts β {"type": "text", ...} and {"type": "image_url", "image_url": {"url": ...}} β for gpt-4o and gpt-4.1. |
{"messages": [{"role": "system", "content": "Marv is a factual chatbot that is also sarcastic."},
{"role": "user", "content": "What's the biggest city in France?"},
{"role": "assistant", "content": "Paris", "weight": 0},
{"role": "user", "content": "Can you be more sarcastic?"},
{"role": "assistant", "content": "Paris, as if everyone doesn't already know that.", "weight": 1}]}
On volume: a fine-tuning job will not proceed with fewer than 10 training examples, Microsoft's guidance is to start with 50 well-crafted examples, and the stated best practice is "hundreds, if not thousands." That is consistent with everything in Lesson 4 β the floor is a technical limit, not a recommendation.
The workflow
Upload the files
import os
from openai import OpenAI
client = OpenAI(
api_key = os.getenv("AZURE_OPENAI_API_KEY"),
base_url = "https://YOUR-RESOURCE-NAME.openai.azure.com/openai/v1/"
)
training_response = client.files.create(file=open("training_set.jsonl", "rb"), purpose="fine-tune")
validation_response = client.files.create(file=open("validation_set.jsonl", "rb"), purpose="fine-tune")
training_file_id = training_response.id
validation_file_id = validation_response.id
Create the training job
response = client.fine_tuning.jobs.create(
training_file = training_file_id,
validation_file = validation_file_id,
model = "gpt-4.1-2025-04-14",
suffix = "my-model", # no dots/periods allowed in Foundry model names
seed = 105,
extra_body = { "trainingType": "GlobalStandard" } # or "Standard", "Developer"
)
job_id = response.id
To set hyperparameters explicitly, use the method parameter:
client.fine_tuning.jobs.create(
training_file = "file-abc123",
model = "gpt-4.1-2025-04-14",
suffix = "my-model",
seed = 105,
method = {
"type": "supervised",
"supervised": {
"hyperparameters": { "n_epochs": 2 }
}
},
extra_body = { "trainingType": "GlobalStandard" }
)
Monitor, and pause if it isn't converging
Metrics are visible during the run, and you can pause a job β which is not just a cancel button. Pausing creates a deployable checkpoint once safety evaluations complete, so you can evaluate what you have, then either deploy it or resume training. This is a genuinely useful cost lever for a run that is obviously drifting.
Deploy
# ARM/REST shape for the deployment itself
sku: { name: "GlobalStandard", capacity: 1 }
You can also enable automatic deployment so a successful run deploys itself, with the deployment named from the generated model name plus your suffix.
The hyperparameters Azure exposes
This is a deliberately short list β a real difference from Track A, where you set a dozen values:
| Parameter | What it does | Guidance |
|---|---|---|
batch_size | Examples per forward/backward pass | Defaults and maxima are model-specific. Larger batches generally work better on larger datasets; larger batch means less frequent updates with lower variance. |
learning_rate_multiplier | Multiplies the model's original pretraining learning rate | Experiment in the range 0.02 to 0.2. Larger learning rates suit larger batches; a smaller value helps avoid overfitting. |
n_epochs | Full passes over the dataset | Defaults are set automatically; the documented defaults run to about 2 epochs. Lesson 9's advice still applies β more epochs is not better. |
seed | Reproducibility | Set it explicitly. Same seed and parameters should reproduce results. |
learning_rate_multiplier harder to reason about than Unsloth's absolute 2e-4. Change one thing at a time, and only once you have a baseline number.
Training tiers β the cost/risk dial
| Tier | What it buys | What it costs you |
|---|---|---|
| Standard | Training in your resource's region. Data residency guarantees. | Highest price. Use when residency is a hard requirement. |
| Global (GlobalStandard) | Lower price, faster queue times by using capacity beyond your region. | Data and weights are copied outside your region. The documented recommendation when residency isn't a constraint. |
| Developer | "Significant cost savings" by using idle capacity. | No latency or SLA guarantees, jobs may be preempted and resumed, no residency guarantee. For experimentation and price-sensitive work. |
RFT: the method that behaves differently
Reinforcement fine-tuning optimises behaviour from reward signals rather than labelled answers. You supply graders β which can be deterministic code (did the unit test pass?) or another model acting as a judge β and the model learns from its own sampled outputs.
| Aspect | SFT / DPO | RFT |
|---|---|---|
| What you provide | Ideal inputβoutput pairs (and preference pairs for DPO) | Prompts plus graders that score outputs |
| Billing basis | Training tokens Γ epochs Γ price per token | Training hours Γ hourly rate (plus grader tokens) |
| Cost control | Shrink the dataset, cut epochs | reasoning_effort low, fewer validation samples and eval_samples, smallest adequate grader, compute_multiplier tuning, pause/cancel monitoring |
| Safety net | β | Per-job billing is capped at $5,000. At the cap, training pauses and a deployable checkpoint is created; you decide whether to resume, after which billing continues uncapped. |
| Best for | Teaching a format, tone, or task | Complex, dynamic behaviour where a correct answer is hard to write but easy to check |
reasoning_effort low until you have measured one run's actual cost.
Serving: four deployment types
| Deployment type | Billing | Notes |
|---|---|---|
| Standard | Per-token (same as base model) + $1.70/hour | Regional data residency. Best-effort latency. |
| Global Standard | Per-token (same as base model) + hosting fee | No residency guarantee, higher throughput. Usually what you want. |
| Regional Provisioned Throughput | PTU-hours β no per-token, no hourly hosting fee | Latency guarantees for latency-sensitive workloads. Needs regional PTU quota and is billed per your Azure agreements. |
| Developer tier | Per-token, no hourly hosting fee | No residency or availability guarantees, and deployments are removed automatically after 24 hours. Designed for candidate evaluation and proofs of concept β and the cheapest way to smoke-test a fine-tuned model. |
Where the managed path fits
| Choose Azure when⦠| Choose Unsloth when⦠|
|---|---|
| You need frontier-model quality in a specific behaviour | You want a small model at near-zero marginal cost |
| You're already in an Azure tenancy with governance in place | Data must never leave your own hardware |
| Nobody on the team wants to own GPU infrastructure | You want total control over export format and serving stack |
| You want to iterate primarily against an eval harness rather than kernels | You're still discovering what the task even is |
| The audit trail of a managed service matters | You want to own the artifact permanently |
The two tracks also compose well: many teams prototype on a free GPU with Unsloth to nail the dataset and prompt, then run the production job on a managed platform for a proprietary model once the approach is proven. The dataset is portable β the conversational JSONL is identical apart from the BOM.
πΊ Watch:
- Deep Dive: Fine-Tuning in Microsoft Foundry β SFT, DPO, Tool Calling and Cost β MadeForCloud. The most complete walkthrough of this lesson's material.
- Fine-tuning and distillation with Azure AI Foundry β BRK150 β Microsoft Developer. Official, and covers the distillation workflow.
- Fine-Tune LLM Models with Ease on Azure AI Foundry β Tech with Kirk. Portal-first, good for seeing how much is point-and-click.
π§ Knowledge Check
1. What is the one hard file requirement that differs from the Unsloth track and commonly causes validation failures?
2. You want to use o4-mini. Which fine-tuning method are you limited to?
3. You need to evaluate a fine-tuned model end-to-end for one day, cheaply. Which deployment type is purpose-built for this?