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:

ModelMethodsModalityRegions / Data Zones
gpt-4o-mini (2024-07-18)SFTText β†’ textNorth Central US, Sweden Central / US
gpt-4o (2024-08-06)SFT, DPOText + vision β†’ textEast US2, North Central US, Sweden Central / US
gpt-4.1 (2025-04-14)SFT, DPOText + vision β†’ textNorth Central US, Sweden Central / US, EU, Asia
gpt-4.1-mini (2025-04-14)SFT, DPOText β†’ textNorth Central US, Sweden Central / US
gpt-4.1-nano (2025-04-14)SFT, DPOText β†’ textNorth Central US, Sweden Central / US
o4-mini (2025-04-16)RFTText β†’ textEast US2, Sweden Central / US
gpt-5 (2025-08-07)RFTText β†’ textNorth Central US, Sweden Central / US
Ministral-3B, Qwen-32B, Llama-3.3-70B-InstructSFTText β†’ textUS data zone
Read the Methods column carefully. If you want o4-mini, you are doing reinforcement fine-tuning β€” there is no SFT option for it. If you want DPO for preference pairs, gpt-4o-mini is out (SFT only) while gpt-4.1 and gpt-4o support it. gpt-5's fine-tuning is described as "generally available by application only." Model availability moves fast; check the live table before you plan a project around a specific model.

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:

UTF-8 with a byte-order mark (BOM), and under 512 MB per file. Most tools write UTF-8 without a BOM by default. A file that trains fine in Unsloth can be rejected by Azure validation for this alone. In Python: 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:

FeatureHow it works
Multi-turn conversationsPut the whole exchange in one line's messages array. Supported directly.
Per-message weightingAdd "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-tuningUse 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

Step 1

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
Step 2

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" }
)
Step 3

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.

Step 4

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:

ParameterWhat it doesGuidance
batch_sizeExamples per forward/backward passDefaults and maxima are model-specific. Larger batches generally work better on larger datasets; larger batch means less frequent updates with lower variance.
learning_rate_multiplierMultiplies the model's original pretraining learning rateExperiment in the range 0.02 to 0.2. Larger learning rates suit larger batches; a smaller value helps avoid overfitting.
n_epochsFull passes over the datasetDefaults are set automatically; the documented defaults run to about 2 epochs. Lesson 9's advice still applies β€” more epochs is not better.
seedReproducibilitySet it explicitly. Same seed and parameters should reproduce results.
For a first job, take the defaults. Microsoft's own guidance is to use the automatic defaults initially. You do not have visibility into the base model's original learning rate, which makes 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

TierWhat it buysWhat it costs you
StandardTraining 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.

AspectSFT / DPORFT
What you provideIdeal input→output pairs (and preference pairs for DPO)Prompts plus graders that score outputs
Billing basisTraining tokens Γ— epochs Γ— price per tokenTraining hours Γ— hourly rate (plus grader tokens)
Cost controlShrink the dataset, cut epochsreasoning_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 forTeaching a format, tone, or taskComplex, dynamic behaviour where a correct answer is hard to write but easy to check
πŸͺ™ Token angle: RFT is the single easiest way to spend a lot of money by accident, because you are billed by the hour and the model's own generation time is inside that hour. The $5,000 per-job cap is a real safety net, but treat it as a kill switch rather than a budget. Before your first RFT job, set an Azure cost alert well below it, run with the smallest grader that can still judge your task, and keep reasoning_effort low until you have measured one run's actual cost.

Serving: four deployment types

Deployment typeBillingNotes
StandardPer-token (same as base model) + $1.70/hourRegional data residency. Best-effort latency.
Global StandardPer-token (same as base model) + hosting feeNo residency guarantee, higher throughput. Usually what you want.
Regional Provisioned ThroughputPTU-hours β€” no per-token, no hourly hosting feeLatency guarantees for latency-sensitive workloads. Needs regional PTU quota and is billed per your Azure agreements.
Developer tierPer-token, no hourly hosting feeNo 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.
The Developer tier is the finest cost tool in this lesson. A 24-hour auto-expiring deployment with no hourly fee lets you run a real end-to-end evaluation of your fine-tune β€” latency, format compliance, output quality β€” for essentially the token cost and nothing else. Compare that with leaving a Standard deployment up for a month: at $1.70/hour that is roughly $1,224 of hosting before you have sent a single request.

Where the managed path fits

Choose Azure when…Choose Unsloth when…
You need frontier-model quality in a specific behaviourYou want a small model at near-zero marginal cost
You're already in an Azure tenancy with governance in placeData must never leave your own hardware
Nobody on the team wants to own GPU infrastructureYou want total control over export format and serving stack
You want to iterate primarily against an eval harness rather than kernelsYou're still discovering what the task even is
The audit trail of a managed service mattersYou 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:

🧠 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?

Further Reading