|
1 | | -# 4.1 Engineer Context |
| 1 | +# 4.1 Customize The Tone & Style With SFT |
2 | 2 |
|
3 | 3 | !!! quote "BY THE END OF THIS LAB YOU SHOULD HAVE" |
4 | 4 |
|
5 | | - - [X] Completed Task 1 |
6 | | - - [X] Completed Task 2 |
7 | | - - [X] Completed Task 3 |
| 5 | + - [ ] Checked your environment setup and validated Azure OpenAI credentials |
| 6 | + - [ ] Analyzed training and validation datasets for fine-tuning |
| 7 | + - [ ] Uploaded datasets and submitted a fine-tuning job to Azure OpenAI |
| 8 | + - [ ] Monitored the fine-tuning process and reviewed results |
| 9 | + - [ ] Deployed and tested your fine-tuned model |
8 | 10 |
|
9 | | ---- |
| 11 | +--- |
| 12 | + |
| 13 | +In this lab, we'll explore how to customize the tone and style of our Zava assistant using Supervised Fine-Tuning (SFT). While few-shot examples and RAG can improve responses, they also increase prompt lengths, which leads to higher token costs and reduced context window for output. Fine-tuning offers a more efficient approach by teaching the model our desired style with many examples but without the overhead of including them in every prompt. |
| 14 | + |
| 15 | +## Step 1: Check Environment Setup |
| 16 | + |
| 17 | +Before we begin fine-tuning, we need to ensure our environment is correctly set up with the necessary Azure OpenAI credentials. |
| 18 | + |
| 19 | +```python |
| 20 | +import os |
| 21 | + |
| 22 | +openai_key = os.getenv("AZURE_OPENAI_API_KEY") |
| 23 | +openai_endpoint = os.getenv("AZURE_OPENAI_ENDPOINT") |
| 24 | +model_name = "gpt-4.1" |
| 25 | +api_version = os.getenv("AZURE_OPENAI_API_VERSION", "2025-02-01-preview") |
| 26 | + |
| 27 | +if not openai_key or not openai_endpoint: |
| 28 | + print("Error: Missing AZURE_OPENAI_KEY or AZURE_OPENAI_ENDPOINT environment variable.") |
| 29 | + |
| 30 | +print("Using Model:", model_name) |
| 31 | +print("Using API Version:", api_version) |
| 32 | +``` |
| 33 | + |
| 34 | +!!! note |
| 35 | + If you encounter an error, make sure you have set the required environment variables in your `.env` file and they have been properly loaded. |
| 36 | + |
| 37 | +--- |
| 38 | + |
| 39 | +## Step 2: Validate Training Dataset |
| 40 | + |
| 41 | +Next, we'll locate and validate our training and validation datasets to ensure they're properly formatted for fine-tuning. |
| 42 | + |
| 43 | +```python |
| 44 | +# Identify Training and Validation datafiles |
| 45 | +training_file = "../data/basic_sft_training.jsonl" |
| 46 | +validation_file = "../data/basic_sft_validation.jsonl" |
| 47 | + |
| 48 | +# Run preliminary checks |
| 49 | +import json |
| 50 | + |
| 51 | +# Load the training set |
| 52 | +with open(training_file, 'r', encoding='utf-8') as f: |
| 53 | + training_dataset = [json.loads(line) for line in f] |
| 54 | + |
| 55 | +# Training dataset stats |
| 56 | +print("Number of examples in training set:", len(training_dataset)) |
| 57 | +print("First example in training set:") |
| 58 | +for message in training_dataset[0]["messages"]: |
| 59 | + print(message) |
| 60 | + |
| 61 | +# Load the validation set |
| 62 | +with open(validation_file, 'r', encoding='utf-8') as f: |
| 63 | + validation_dataset = [json.loads(line) for line in f] |
| 64 | + |
| 65 | +# Validation dataset stats |
| 66 | +print("\nNumber of examples in validation set:", len(validation_dataset)) |
| 67 | +``` |
| 68 | + |
| 69 | +The output will show you the number of examples in each dataset and a sample of the first example's format. |
| 70 | + |
| 71 | +!!! warning |
| 72 | + Fine-tuning datasets must follow the proper JSONL format with messages containing role/content pairs. Make sure your data follows this structure. |
| 73 | + |
| 74 | +--- |
| 75 | + |
| 76 | +## Step 3: Assess Token Counts For Data |
| 77 | + |
| 78 | +It's important to understand the token distribution in our training data to ensure effective fine-tuning. |
| 79 | + |
| 80 | +```python |
| 81 | +import tiktoken |
| 82 | +import numpy as np |
| 83 | + |
| 84 | +encoding = tiktoken.get_encoding("o200k_base") # default encoding for gpt-4o models |
| 85 | + |
| 86 | +def num_tokens_from_messages(messages, tokens_per_message=3, tokens_per_name=1): |
| 87 | + num_tokens = 0 |
| 88 | + for message in messages: |
| 89 | + num_tokens += tokens_per_message |
| 90 | + for key, value in message.items(): |
| 91 | + num_tokens += len(encoding.encode(value)) |
| 92 | + if key == "name": |
| 93 | + num_tokens += tokens_per_name |
| 94 | + num_tokens += 3 |
| 95 | + return num_tokens |
| 96 | + |
| 97 | +# Add token counting code and print distribution statistics |
| 98 | +``` |
| 99 | + |
| 100 | +This analysis will show you the distribution of total tokens and assistant tokens in your datasets, which helps in understanding the training data characteristics. |
| 101 | + |
| 102 | +--- |
| 103 | + |
| 104 | +## Step 4: Upload Fine-Tuning Data To Cloud |
| 105 | + |
| 106 | +Now we'll create an Azure OpenAI client and upload our training and validation datasets. |
| 107 | + |
| 108 | +```python |
| 109 | +from openai import AzureOpenAI |
| 110 | + |
| 111 | +client = AzureOpenAI( |
| 112 | + azure_endpoint = os.getenv("AZURE_OPENAI_ENDPOINT"), |
| 113 | + api_key = os.getenv("AZURE_OPENAI_API_KEY"), |
| 114 | + api_version = os.getenv("AZURE_OPENAI_API_VERSION") |
| 115 | +) |
| 116 | + |
| 117 | +# Upload the training and validation dataset files |
| 118 | +training_response = client.files.create( |
| 119 | + file = open(training_file, "rb"), purpose="fine-tune" |
| 120 | +) |
| 121 | +training_file_id = training_response.id |
| 122 | + |
| 123 | +validation_response = client.files.create( |
| 124 | + file = open(validation_file, "rb"), purpose="fine-tune" |
| 125 | +) |
| 126 | +validation_file_id = validation_response.id |
| 127 | + |
| 128 | +print("Training file ID:", training_file_id) |
| 129 | +print("Validation file ID:", validation_file_id) |
| 130 | +``` |
| 131 | + |
| 132 | +!!! note |
| 133 | + You can visit the Azure AI Foundry Portal and look under your Azure AI Project's 'Data Files' tab to see the uploaded files. |
| 134 | + |
| 135 | +--- |
| 136 | + |
| 137 | +## Step 5: Submit The Fine-Tuning Job |
| 138 | + |
| 139 | +With our data uploaded, we can now submit the fine-tuning job. |
| 140 | + |
| 141 | +```python |
| 142 | +# Submit fine-tuning training job |
| 143 | +response = client.fine_tuning.jobs.create( |
| 144 | + training_file=training_file_id, |
| 145 | + validation_file=validation_file_id, |
| 146 | + model="gpt-4.1", # Enter base model name |
| 147 | + seed = 105, # for reproducibility |
| 148 | + hyperparameters={ |
| 149 | + "learning_rate_multiplier": 2.0, # Higher LR for faster training |
| 150 | + "n_epochs": 3, # Reduced epochs (default is often overkill) |
| 151 | + "batch_size": 16, # Larger batch for stability |
| 152 | + } |
| 153 | +) |
| 154 | + |
| 155 | +job_id = response.id |
| 156 | +print("Job ID:", response.id) |
| 157 | +``` |
| 158 | + |
| 159 | +!!! warning |
| 160 | + Currently gpt-4.1 can be fine-tuned only in Sweden Central and North Central US regions. Make sure your Azure OpenAI resource is in one of these regions. |
| 161 | + |
| 162 | +--- |
| 163 | + |
| 164 | +## Step 6: Track Fine-Tuning Job Status |
| 165 | + |
| 166 | +Fine-tuning can take some time. We'll monitor the progress of our job. |
| 167 | + |
| 168 | +```python |
| 169 | +from IPython.display import clear_output |
| 170 | +import time |
| 171 | + |
| 172 | +start_time = time.time() |
| 173 | + |
| 174 | +# Get the status of our fine-tuning job |
| 175 | +response = client.fine_tuning.jobs.retrieve(job_id) |
| 176 | +status = response.status |
| 177 | + |
| 178 | +# Poll job status every 10 seconds |
| 179 | +while status not in ["succeeded", "failed"]: |
| 180 | + time.sleep(10) |
| 181 | + response = client.fine_tuning.jobs.retrieve(job_id) |
| 182 | + print(f'Status: {status}') |
| 183 | + status = response.status |
| 184 | + |
| 185 | +print(f'Fine-tuning job {job_id} finished with status: {status}') |
| 186 | +``` |
| 187 | + |
| 188 | +--- |
| 189 | + |
| 190 | +## Step 7: List Fine-Tuning Events and Checkpoints |
| 191 | + |
| 192 | +Once the job completes, we can review the fine-tuning events and checkpoints. |
| 193 | + |
| 194 | +```python |
| 195 | +# Review fine-tuning events |
| 196 | +response = client.fine_tuning.jobs.list_events(fine_tuning_job_id=job_id, limit=10) |
| 197 | + |
| 198 | +# List checkpoints |
| 199 | +response = client.fine_tuning.jobs.checkpoints.list(job_id) |
| 200 | +``` |
| 201 | + |
| 202 | +These provide insights into the training process and model checkpoints created during fine-tuning. |
| 203 | + |
| 204 | +--- |
| 205 | + |
| 206 | +## Step 8: Retrieve Fine-Tuned Model Name |
| 207 | + |
| 208 | +After successful fine-tuning, we'll retrieve the name of our fine-tuned model. |
| 209 | + |
| 210 | +```python |
| 211 | +response = client.fine_tuning.jobs.retrieve(job_id) |
| 212 | +fine_tuned_model = response.fine_tuned_model |
| 213 | +print("Fine-tuned model name:", fine_tuned_model) |
| 214 | +``` |
| 215 | + |
| 216 | +--- |
| 217 | + |
| 218 | +## Step 9: Deploy and Test Your Fine-Tuned Model |
| 219 | + |
| 220 | +For testing purposes, deploy your model using the developer tier in the Azure AI Foundry Portal. |
| 221 | + |
| 222 | +### Example Test Prompts and Responses: |
| 223 | + |
| 224 | +> **Prompt 1**: What kind of paint should I buy for my outdoor deck? |
| 225 | +> |
| 226 | +> **Response**: 🪵 Deck protection options! Semi-Transparent Deck Stain at 38 enhances wood grain, or Deck & Fence Stain at 36 for UV protection? |
| 227 | +
|
| 228 | +> **Prompt 2**: I'm painting over rust - what spray paint should I use? |
| 229 | +> |
| 230 | +> **Response**: 👍 Right choice! Rust Prevention Spray at $13 applies directly over rust with long-lasting protection. Primer recommendation? |
| 231 | +
|
| 232 | +### Insights from Testing |
| 233 | + |
| 234 | +Our fine-tuned model now follows the Zava guidelines for "polite, factual and helpful" responses: |
| 235 | +- Every response starts with an emoji |
| 236 | +- The first sentence acknowledges the user ("polite") |
| 237 | +- The second sentence provides information ("factual") |
| 238 | +- The final sentence offers follow-up assistance ("helpful") |
| 239 | + |
| 240 | +Most importantly, we've achieved these succinct, styled responses without adding few-shot examples in every prompt, saving both token costs and processing latency. |
| 241 | + |
| 242 | +--- |
| 243 | + |
| 244 | +## Teardown |
| 245 | + |
| 246 | +Once you are done with this lab, don't forget to tear down the infrastructure. The developer tier model will be automatically deleted after 24 hours, but it's better to proactively delete the resource group and release all model quota. |
| 247 | + |
| 248 | +--- |
| 249 | + |
| 250 | +<div style="display: flex; align-items: center; justify-content: left; padding: 5px; height: 40px; background: linear-gradient(90deg, #7873f5 0%, #ff6ec4 100%); border-radius: 8px; box-shadow: 0 2px 8px rgba(0,0,0,0.12); font-size: 1.5em; font-weight: bold; color: #fff;"> |
| 251 | + Next: Be More Cost-Effective With Distillation |
| 252 | +</div> |
0 commit comments