# Section 3: Fine-Tuning - Customizing Models for Specific Tasks

## Table of Contents
1. [Introduction to Fine-Tuning](#introduction-to-fine-tuning)
2. [Why Fine-Tuning Matters](#why-fine-tuning-matters)
3. [Types of Fine-Tuning](#types-of-fine-tuning)
4. [Fine-Tuning with Microsoft Olive](#fine-tuning-with-microsoft-olive)
5. [Hands-On Examples](#hands-on-examples)
6. [Best Practices and Guidelines](#best-practices-and-guidelines)
7. [Advanced Techniques](#advanced-techniques)
8. [Evaluation and Monitoring](#evaluation-and-monitoring)
9. [Common Challenges and Solutions](#common-challenges-and-solutions)
10. [Conclusion](#conclusion)

## Introduction to Fine-Tuning

**Fine-tuning** is a powerful machine learning technique that involves adapting a pre-trained model to perform specific tasks or work with specialized datasets. Rather than training a model from scratch, fine-tuning leverages the knowledge already learned by a pre-trained model and adjusts it for your particular use case.

### What is Fine-Tuning?

Fine-tuning is a form of **transfer learning** where you:
- Start with a pre-trained model that has learned general patterns from large datasets
- Adjust the model's internal parameters using your specific dataset
- Retain the valuable knowledge while specializing the model for your task

Think of it like teaching a skilled chef to cook a new cuisine - they already understand cooking fundamentals, but need to learn specific techniques and flavors for the new style.

### Key Benefits

- **Time Efficiency**: Significantly faster than training from scratch
- **Data Efficiency**: Requires smaller datasets to achieve good performance
- **Cost-Effective**: Lower computational requirements
- **Better Performance**: Often achieves superior results compared to training from scratch
- **Resource Optimization**: Makes powerful AI accessible to smaller teams and organizations

## Why Fine-Tuning Matters

### Real-World Applications

Fine-tuning is essential in numerous scenarios:

**1. Domain Adaptation**
- Medical AI: Adapting general language models for medical terminology and clinical notes
- Legal Tech: Specializing models for legal document analysis and contract review
- Financial Services: Customizing models for financial report analysis and risk assessment

**2. Task Specialization**
- Content Generation: Fine-tuning for specific writing styles or tones
- Code Generation: Adapting models for particular programming languages or frameworks
- Translation: Improving performance for specific language pairs or technical domains

**3. Corporate Applications**
- Customer Service: Creating chatbots that understand company-specific terminology
- Internal Documentation: Building AI assistants familiar with organizational processes
- Industry-Specific Solutions: Developing models that understand sector-specific jargon and workflows

## Types of Fine-Tuning

### 1. Full Fine-Tuning (Instruction Fine-Tuning)

In full fine-tuning, all model parameters are updated during training. This approach:
- Provides maximum flexibility and performance potential
- Requires significant computational resources
- Results in a completely new version of the model
- Best for scenarios where you have substantial training data and computational resources

### 2. Parameter-Efficient Fine-Tuning (PEFT)

PEFT methods update only a small subset of parameters, making the process more efficient:

#### Low-Rank Adaptation (LoRA)
- Adds small trainable rank decomposition matrices to existing weights
- Dramatically reduces the number of trainable parameters
- Maintains performance close to full fine-tuning
- Enables easy switching between different adaptations

#### QLoRA (Quantized LoRA)
- Combines LoRA with quantization techniques
- Further reduces memory requirements
- Enables fine-tuning of larger models on consumer hardware
- Balances efficiency with performance

#### Adapters
- Insert small neural networks between existing layers
- Allow targeted fine-tuning while keeping base model frozen
- Enable modular approach to model customization

### 3. Task-Specific Fine-Tuning

Focuses on adapting models for specific downstream tasks:
- **Classification**: Adjusting models for categorization tasks
- **Generation**: Optimizing for content creation and text generation
- **Extraction**: Fine-tuning for information extraction and named entity recognition
- **Summarization**: Specializing models for document summarization

## Fine-Tuning with Microsoft Olive

Microsoft Olive is a comprehensive model optimization toolkit that simplifies the fine-tuning process while providing enterprise-grade features.

### What is Microsoft Olive?

Microsoft Olive is an open-source model optimization tool that:
- Streamlines fine-tuning workflows for various hardware targets
- Provides built-in support for popular model architectures (Llama, Phi, Qwen, Gemma)
- Offers both cloud and local deployment options
- Integrates seamlessly with Azure ML and other Microsoft AI services
- Supports automatic optimization and quantization

### Key Features

- **Hardware-Aware Optimization**: Automatically optimizes models for specific hardware (CPU, GPU, NPU)
- **Multi-Format Support**: Works with PyTorch, Hugging Face, and ONNX models
- **Automated Workflows**: Reduces manual configuration and trial-and-error
- **Enterprise Integration**: Built-in support for Azure ML and cloud deployments
- **Extensible Architecture**: Allows custom optimization techniques

### Installation and Setup

#### Basic Installation

```bash
# Create a virtual environment
python -m venv olive-env
source olive-env/bin/activate  # On Windows: olive-env\Scripts\activate

# Install Olive with auto-optimization features
pip install olive-ai[auto-opt]

# Install additional dependencies
pip install transformers onnxruntime-genai
```

#### Optional Dependencies

```bash
# For CPU optimization
pip install olive-ai[cpu]

# For GPU optimization
pip install olive-ai[gpu]

# For DirectML (Windows)
pip install olive-ai[directml]

# For Azure ML integration
pip install olive-ai[azureml]
```

#### Verify Installation

```bash
# Check Olive CLI is available
olive --help

# Verify installation
python -c "import olive; print('Olive installed successfully')"
```

## Hands-On Examples

### Example 1: Basic Fine-Tuning with Olive CLI

This example demonstrates fine-tuning a small language model for phrase classification:

#### Step 1: Prepare Your Environment

```bash
# Set up the environment
mkdir fine-tuning-project
cd fine-tuning-project

# Download sample data (optional - Olive can fetch data automatically)
huggingface-cli login  # If using private datasets
```

#### Step 2: Fine-Tune the Model

```bash
# Basic fine-tuning command
olive finetune \
  --model_name_or_path meta-llama/Llama-3.2-1B-Instruct \
  --trust_remote_code \
  --output_path models/llama/ft \
  --data_name xxyyzzz/phrase_classification \
  --text_template "<|start_header_id|>user<|end_header_id|>\n{phrase}<|eot_id|><|start_header_id|>assistant<|end_header_id|>\n{tone}" \
  --method lora \
  --max_steps 100 \
  --log_level 1
```

#### Step 3: Optimize for Deployment

```bash
# Convert to ONNX format for optimized inference
olive auto-opt \
  --model_name_or_path models/llama/ft/model \
  --adapter_path models/llama/ft/adapter \
  --device cpu \
  --provider CPUExecutionProvider \
  --use_ort_genai \
  --output_path models/llama/onnx \
  --log_level 1
```

### Example 2: Advanced Configuration with Custom Dataset

#### Step 1: Prepare Custom Dataset

Create a JSON file with your training data:

```json
[
  {
    "input": "What is machine learning?",
    "output": "Machine learning is a subset of artificial intelligence that enables computers to learn and improve from experience without being explicitly programmed."
  },
  {
    "input": "Explain neural networks",
    "output": "Neural networks are computing systems inspired by biological neural networks that learn from data through interconnected nodes or neurons."
  }
]
```

#### Step 2: Create Configuration File

```yaml
# olive-config.yaml
model:
  type: PyTorchModel
  config:
    model_path: "microsoft/DialoGPT-medium"
    task: "text-generation"

data_configs:
  - name: "custom_dataset"
    type: "HuggingfaceContainer"
    load_dataset_config:
      data_files: "path/to/your/dataset.json"
      split: "train"
    pre_process_data_config:
      text_template: "User: {input}\nAssistant: {output}"

passes:
  lora:
    type: LoRA
    config:
      r: 16
      lora_alpha: 32
      target_modules: ["c_attn", "c_proj"]
      modules_to_save: ["ln_f", "lm_head"]
```

#### Step 3: Execute Fine-Tuning

```bash
# Run with custom configuration
olive run --config olive-config.yaml --setup
```

### Example 3: QLoRA Fine-Tuning for Memory Efficiency

```bash
# Fine-tune with QLoRA for better memory efficiency
olive finetune \
  --method qlora \
  --model_name_or_path meta-llama/Meta-Llama-3-8B \
  --data_name nampdn-ai/tiny-codes \
  --train_split "train[:4096]" \
  --eval_split "train[4096:4224]" \
  --text_template "### Language: {programming_language} \n### Question: {prompt} \n### Answer: {response}" \
  --per_device_train_batch_size 16 \
  --per_device_eval_batch_size 16 \
  --max_steps 150 \
  --logging_steps 50 \
  --output_path adapters/tiny-codes
```

## Best Practices and Guidelines

### Data Preparation

**1. Data Quality Over Quantity**
- Prioritize high-quality, diverse examples over large volumes of poor data
- Ensure data is representative of your target use case
- Clean and preprocess data consistently

**2. Data Format and Templates**
- Use consistent formatting across all training examples
- Create clear input-output templates that match your use case
- Include appropriate instruction formatting for instruction-tuned models

**3. Dataset Splitting**
- Reserve 10-20% of data for validation
- Maintain similar distributions across train/validation splits
- Consider stratified sampling for classification tasks

### Training Configuration

**1. Learning Rate Selection**
- Start with smaller learning rates (1e-5 to 1e-4) for fine-tuning
- Use learning rate scheduling for better convergence
- Monitor loss curves to adjust rates accordingly

**2. Batch Size Optimization**
- Balance batch size with available memory
- Use gradient accumulation for larger effective batch sizes
- Consider the relationship between batch size and learning rate

**3. Training Duration**
- Monitor validation metrics to avoid overfitting
- Use early stopping when validation performance plateaus
- Save checkpoints regularly for recovery and analysis

### Model Selection

**1. Base Model Choice**
- Select models pre-trained on similar domains when possible
- Consider model size relative to your computational constraints
- Evaluate licensing requirements for commercial use

**2. Fine-Tuning Method Selection**
- Use LoRA/QLoRA for resource-constrained environments
- Choose full fine-tuning when maximum performance is critical
- Consider adapter-based approaches for multiple task scenarios

### Resource Management

**1. Hardware Optimization**
- Choose appropriate hardware for your model size and method
- Utilize GPU memory efficiently with gradient checkpointing
- Consider cloud-based solutions for larger models

**2. Memory Management**
- Use mixed precision training when available
- Implement gradient accumulation for memory constraints
- Monitor GPU memory usage throughout training

## Advanced Techniques

### Multi-Adapter Training

Train multiple adapters for different tasks while sharing the base model:

```bash
# Train multiple LoRA adapters
olive finetune --method lora --task_name "classification" --output_path adapters/classifier
olive finetune --method lora --task_name "generation" --output_path adapters/generator

# Generate multi-adapter ONNX model
olive generate-adapter \
  --base_model_path models/base \
  --adapter_paths adapters/classifier,adapters/generator \
  --output_path models/multi-adapter
```

### Hyperparameter Optimization

Implement systematic hyperparameter tuning:

```yaml
# hyperparameter-search.yaml
search_strategy:
  type: "random"
  num_trials: 20

search_space:
  learning_rate:
    type: "float"
    low: 1e-6
    high: 1e-3
    log: true
  
  lora_r:
    type: "int"
    low: 8
    high: 64
  
  batch_size:
    type: "choice"
    values: [8, 16, 32]
```

### Custom Loss Functions

Implement domain-specific loss functions:

```python
# custom_loss.py
import torch
import torch.nn as nn

class CustomContrastiveLoss(nn.Module):
    def __init__(self, margin=1.0):
        super(CustomContrastiveLoss, self).__init__()
        self.margin = margin
        
    def forward(self, output1, output2, label):
        euclidean_distance = nn.functional.pairwise_distance(output1, output2)
        loss_contrastive = torch.mean((1-label) * torch.pow(euclidean_distance, 2) +
                                    (label) * torch.pow(torch.clamp(self.margin - euclidean_distance, min=0.0), 2))
        return loss_contrastive
```

## Evaluation and Monitoring

### Metrics and Evaluation

**1. Standard Metrics**
- **Accuracy**: Overall correctness for classification tasks
- **Perplexity**: Language modeling quality measure
- **BLEU/ROUGE**: Text generation and summarization quality
- **F1 Score**: Balanced precision and recall for classification

**2. Domain-Specific Metrics**
- **Task-Specific Benchmarks**: Use established benchmarks for your domain
- **Human Evaluation**: Include human assessment for subjective tasks
- **Business Metrics**: Align with actual business objectives

**3. Evaluation Setup**

```python
# evaluation_script.py
from transformers import AutoTokenizer, AutoModelForCausalLM
from datasets import load_dataset
import torch

def evaluate_model(model_path, test_dataset, metric_type="accuracy"):
    """
    Evaluate fine-tuned model performance
    """
    tokenizer = AutoTokenizer.from_pretrained(model_path)
    model = AutoModelForCausalLM.from_pretrained(model_path)
    
    # Evaluation logic here
    results = {}
    
    for example in test_dataset:
        # Process example and calculate metrics
        pass
    
    return results
```

### Monitoring Training Progress

**1. Loss Tracking**
```bash
# Enable detailed logging
olive finetune \
  --logging_steps 10 \
  --eval_steps 50 \
  --save_steps 100 \
  --logging_dir ./logs \
  --report_to tensorboard
```

**2. Validation Monitoring**
- Track validation loss alongside training loss
- Monitor for signs of overfitting (validation loss increasing while training loss decreases)
- Use early stopping based on validation metrics

**3. Resource Monitoring**
- Monitor GPU/CPU utilization
- Track memory usage patterns
- Monitor training speed and throughput

## Common Challenges and Solutions

### Challenge 1: Overfitting

**Symptoms:**
- Training loss continues to decrease while validation loss increases
- Large gap between training and validation performance
- Poor generalization to new data

**Solutions:**
```yaml
# Regularization techniques
passes:
  lora:
    type: LoRA
    config:
      r: 16  # Reduce rank to prevent overfitting
      lora_alpha: 16  # Lower alpha value
      lora_dropout: 0.1  # Add dropout
      weight_decay: 0.01  # L2 regularization
```

### Challenge 2: Memory Limitations

**Solutions:**
- Use gradient checkpointing
- Implement gradient accumulation
- Choose parameter-efficient methods (LoRA, QLoRA)
- Utilize model parallelism for large models

```bash
# Memory-efficient training
olive finetune \
  --method qlora \
  --gradient_checkpointing true \
  --per_device_train_batch_size 1 \
  --gradient_accumulation_steps 16
```

### Challenge 3: Slow Training

**Solutions:**
- Optimize data loading pipelines
- Use mixed precision training
- Implement efficient batching strategies
- Consider distributed training for large datasets

```yaml
# Performance optimization
training_config:
  fp16: true  # Mixed precision
  dataloader_num_workers: 4
  optim: "adamw_torch"
  lr_scheduler_type: "cosine"
```

### Challenge 4: Poor Performance

**Diagnosis Steps:**
1. Verify data quality and formatting
2. Check learning rate and training duration
3. Evaluate base model choice
4. Review preprocessing and tokenization

**Solutions:**
- Increase training data diversity
- Adjust learning rate schedule
- Try different base models
- Implement data augmentation techniques

## Conclusion

Fine-tuning is a powerful technique that democratizes access to state-of-the-art AI capabilities. By leveraging tools like Microsoft Olive, organizations can efficiently adapt pre-trained models to their specific needs while optimizing for performance and resource constraints.

### Key Takeaways

1. **Choose the Right Approach**: Select fine-tuning methods based on your computational resources and performance requirements
2. **Data Quality Matters**: Invest in high-quality, representative training data
3. **Monitor and Iterate**: Continuously evaluate and improve your models
4. **Leverage Tools**: Use frameworks like Olive to simplify and optimize the process
5. **Consider Deployment**: Plan for model optimization and deployment from the beginning


## ➡️ What's next

- [04: Deployment - Production-Ready Model Implementation](./04.SLMOps.Deployment.md)
