Official docs: Create a fine-tuning job
This lesson covers the end-to-end workflow: upload data, create a job, monitor training, deploy, and run inference.
"""End-to-end fine-tuning workflow with the Azure OpenAI Python SDK."""
import os
import time
from openai import AzureOpenAI
client = AzureOpenAI(
azure_endpoint=os.environ["AZURE_OPENAI_ENDPOINT"],
api_key=os.environ["AZURE_OPENAI_API_KEY"],
api_version="2025-03-01-preview",
)
# -- Step 1: Upload training & validation files --
training_file = client.files.create(
file=open("train.jsonl", "rb"),
purpose="fine-tune",
)
print(f"Training file uploaded: {training_file.id} (status: {training_file.status})")
validation_file = client.files.create(
file=open("val.jsonl", "rb"),
purpose="fine-tune",
)
print(f"Validation file uploaded: {validation_file.id}")
# Wait for file processing
for file_obj in [training_file, validation_file]:
while True:
status = client.files.retrieve(file_obj.id).status
if status == "processed":
break
elif status == "error":
raise RuntimeError(f"File {file_obj.id} processing failed.")
print(f" File {file_obj.id}: {status}...")
time.sleep(5)
# -- Step 2: Create fine-tuning job --
job = client.fine_tuning.jobs.create(
training_file=training_file.id,
validation_file=validation_file.id,
model="gpt-4o-mini-2024-07-18",
hyperparameters={
"n_epochs": 3,
"batch_size": "auto",
"learning_rate_multiplier": "auto",
},
suffix="contoso-support", # custom name suffix for the model
)
print(f"Fine-tuning job created: {job.id}")
# -- Step 3: Monitor progress --
while True:
job = client.fine_tuning.jobs.retrieve(job.id)
print(f" Status: {job.status}")
if job.status == "running":
# Fetch recent training events
events = client.fine_tuning.jobs.list_events(job.id, limit=3)
for event in events.data:
print(f" [{event.created_at}] {event.message}")
if job.status in ("succeeded", "failed", "cancelled"):
break
time.sleep(60)
if job.status != "succeeded":
raise RuntimeError(f"Job ended with status: {job.status} -- {job.error}")
fine_tuned_model = job.fine_tuned_model
print(f"\nFine-tuned model: {fine_tuned_model}")
# -- Step 4: Deploy the model (via REST -- SDK deployment varies) --
# Option A: Deploy via Azure CLI (run in terminal):
# az cognitiveservices account deployment create \
# --resource-group <rg> \
# --name <resource> \
# --deployment-name contoso-support-ft \
# --model-name <fine_tuned_model> \
# --model-version "1" \
# --model-format OpenAI \
# --sku-capacity 1 \
# --sku-name "Standard"
# Option B: Deploy via Python + requests (REST API):
import requests
subscription_id = os.environ["AZURE_SUBSCRIPTION_ID"]
resource_group = os.environ["AZURE_RESOURCE_GROUP"]
resource_name = os.environ["AZURE_OPENAI_RESOURCE_NAME"]
deployment_name = "contoso-support-ft"
mgmt_token = os.environ["AZURE_MGMT_TOKEN"] # from az account get-access-token
deploy_url = (
f"https://management.azure.com/subscriptions/{subscription_id}"
f"/resourceGroups/{resource_group}"
f"/providers/Microsoft.CognitiveServices/accounts/{resource_name}"
f"/deployments/{deployment_name}"
f"?api-version=2024-10-01"
)
deploy_body = {
"sku": {"name": "Standard", "capacity": 1},
"properties": {
"model": {
"format": "OpenAI",
"name": fine_tuned_model,
"version": "1",
}
},
}
resp = requests.put(
deploy_url,
headers={
"Authorization": f"Bearer {mgmt_token}",
"Content-Type": "application/json",
},
json=deploy_body,
)
print(f"Deploy response: {resp.status_code}")
# -- Step 5: Use the fine-tuned model --
response = client.chat.completions.create(
model=deployment_name, # use deployment name, not the model ID
messages=[
{"role": "system", "content": "You are a customer service agent for Contoso Electronics."},
{"role": "user", "content": "I want to return my headphones."},
],
temperature=0.3,
max_tokens=300,
)
print(f"\nAssistant: {response.choices[0].message.content}")
| Parameter | Default | Range | Guidance |
|---|---|---|---|
n_epochs |
auto (typically 3) |
1-50 | Start with auto. Increase for small datasets; decrease if validation loss rises. |
batch_size |
auto |
1-256 | Larger = faster but uses more memory. auto picks based on dataset size. |
learning_rate_multiplier |
auto |
0.01-5.0 | Lower = more conservative. Increase if model is not learning enough; decrease if overfitting. |
auto Defaultsn_epochs:
- If dataset <= 100 examples -> ~10-15 epochs
- If dataset <= 1000 examples -> ~3-4 epochs
- If dataset > 1000 examples -> ~2 epochs
batch_size:
- Typically 1-4 for small datasets
- Scales with dataset size
learning_rate_multiplier:
- Scales inversely with batch_size
Tip: Always start with
autofor all three. Only tune manually after reviewing training metrics.
Overfitting occurs when the model memorizes training data rather than learning generalizable patterns.
| Metric | Healthy | Overfitting |
|---|---|---|
| Training loss | Decreasing steadily | Decreasing to near zero |
| Validation loss | Decreasing or stable | Increasing after initial decrease |
| Gap (train - val loss) | Small gap | Large and growing gap |
| Output diversity | Varied responses | Repetitive / verbatim training data |
1. Reduce n_epochs -> Stop training earlier
2. Increase dataset size -> More diverse examples
3. Lower LR multiplier -> Smaller weight updates
4. Add validation file -> Monitor val loss to detect early
5. Review data quality -> Remove duplicates, fix inconsistencies
"""Check training metrics from job events."""
events = client.fine_tuning.jobs.list_events(job.id, limit=50)
for event in events.data:
if "training" in event.message.lower() or "validation" in event.message.lower():
print(f"[Step] {event.message}")
ft:)# Deploy the fine-tuned model
az cognitiveservices account deployment create \
--resource-group myResourceGroup \
--name myOpenAIResource \
--deployment-name contoso-support-ft \
--model-name "ft:gpt-4o-mini-2024-07-18:contoso-support:abc123" \
--model-version "1" \
--model-format OpenAI \
--sku-capacity 1 \
--sku-name "Standard"
# Verify the deployment
az cognitiveservices account deployment show \
--resource-group myResourceGroup \
--name myOpenAIResource \
--deployment-name contoso-support-ft
# Deploy via ARM REST API
curl -X PUT \
"https://management.azure.com/subscriptions/{sub}/resourceGroups/{rg}/providers/Microsoft.CognitiveServices/accounts/{resource}/deployments/{deploymentName}?api-version=2024-10-01" \
-H "Authorization: Bearer $MGMT_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"sku": {"name": "Standard", "capacity": 1},
"properties": {
"model": {
"format": "OpenAI",
"name": "ft:gpt-4o-mini-2024-07-18:contoso-support:abc123",
"version": "1"
}
}
}'
Tip: Fine-tuned model weights persist even after deleting a deployment. You can redeploy later without retraining.
jobs = client.fine_tuning.jobs.list()
for j in jobs.data:
print(f"{j.id} model={j.model} status={j.status} "
f"fine_tuned_model={j.fine_tuned_model}")
client.fine_tuning.jobs.cancel("ftjob-abc123")
print("Job cancelled.")
# Delete the fine-tuned model (removes weights -- cannot be undone)
client.models.delete("ft:gpt-4o-mini-2024-07-18:contoso-support:abc123")
print("Model deleted.")
files = client.files.list(purpose="fine-tune")
for f in files.data:
print(f"{f.id} {f.filename} {f.status} {f.bytes} bytes")
client.files.delete("file-abc123")
+--------------+ +--------------+ +--------------+
| Prepare |---->| Upload |---->| Create Job |
| JSONL data | | files | | |
+--------------+ +--------------+ +------+-------+
|
v
+--------------+ +--------------+ +--------------+
| Inference |<----| Deploy |<----| Monitor |
| | | model | | training |
+--------------+ +--------------+ +--------------+
| Step | Method | Key SDK Call |
|---|---|---|
| Upload | SDK | client.files.create(file, purpose="fine-tune") |
| Create Job | SDK | client.fine_tuning.jobs.create(...) |
| Monitor | SDK | client.fine_tuning.jobs.retrieve(job_id) |
| List Events | SDK | client.fine_tuning.jobs.list_events(job_id) |
| Deploy | CLI / REST | az cognitiveservices account deployment create |
| Inference | SDK | client.chat.completions.create(model=deployment_name) |
| Cleanup | SDK | client.models.delete(model_id) |
Next –> Lesson 3: Advanced Fine-Tuning