Fine-Tune a Code Review Classifier
Fine-tune a 7B LLM to classify pull request diffs using QLoRA.
Introduction
30 Second Summary
Code reviews are one of the most time-consuming parts of software development. Every pull request needs a human to read the diff, understand the changes, and decide whether to approve or request changes.
In this project, you will fine-tune a large language model to automatically classify pull request diffs as APPROVE or REQUEST_CHANGES. You'll use QLoRA to train a 7-billion-parameter model on real code review data, then publish your trained model on Hugging Face Hub.
What You'll Build
A fine-tuned code review classifier that takes a pull request diff and predicts whether it should be approved or requires changes, trained on 960 real PR diffs from 123 repositories.
By the end of this project, you'll have:
- A Google Colab notebook with the complete QLoRA fine-tuning pipeline, from data preparation through evaluation.
- A trained LoRA adapter published on Hugging Face Hub with a model card and evaluation metrics.
- Hands-on understanding of the end-to-end workflow: data formatting, 4-bit quantization, supervised fine-tuning, and model evaluation.
- Secret Mission: Build a shareable Gradio demo app for live code review predictions.
Are there any prerequisites?
You should be comfortable with Python and familiar with basic ML concepts. You'll also need a Hugging Face account with a write-access token (free to create).
No local GPU is required. The entire project runs in Google Colab, which provides a free T4 GPU.
Set Up Google Colab Environment
Fine-tuning a 7-billion-parameter model sounds like it needs expensive hardware. It does not. Google Colab gives you free access to an NVIDIA T4 GPU with 16 GB of VRAM, enough to run the entire QLoRA pipeline from your browser.
In this step, you will set up Colab with GPU acceleration, install the Hugging Face ML stack, and authenticate so you can push your trained model later.
In this step, get ready to:
- Open a Colab notebook with a T4 GPU and verify it with nvidia-smi.
- Install the ML libraries needed for QLoRA fine-tuning and log into Hugging Face Hub.
- Import all modules and confirm library versions.
Open a Colab notebook and enable the T4 GPU
QLoRA uses 4-bit quantization to compress a 7B model into roughly 3.5 GB of GPU memory. The T4's 16 GB gives plenty of headroom for the model, LoRA adapters, and optimizer states.
- Open your browser and go to colab.research.google.com.
- Click New Notebook to create a fresh notebook.
- In the top menu, click Runtime.
- Click Change runtime type.
- Under Hardware accelerator, select T4 GPU.
- Click Save.
Colab reconnects your notebook to a Linux instance with an NVIDIA T4 attached.
- Verify GPU availability by running this command in the first cell:
!nvidia-smi
What should I see?
You should see output showing a Tesla T4 GPU with approximately 15 GB of memory. The table displays GPU name, temperature, memory usage, and driver version.
- Confirm the output shows Tesla T4 in the GPU Name column.
Not seeing a GPU?
Make sure you selected T4 GPU (not CPU or TPU) under Hardware accelerator. Go back to Runtime and click Change runtime type to double-check.
If you see a message about GPU unavailability, free GPUs are limited by demand. Try again in a few minutes or at a less busy time of day.
help me troubleshoot why my Google Colab notebook is not detecting a T4 GPU
Install libraries and log into Hugging Face
The QLoRA pipeline needs several Hugging Face libraries working together. You need transformers for model loading, peft for LoRA adapters, trl for the SFT trainer, and bitsandbytes for 4-bit quantization.
- Install the required libraries by running this in a new cell:
!pip install -U transformers peft trl bitsandbytes datasets accelerate scikit-learn
What does this command do?
The -U flag upgrades each package to the latest compatible version. This avoids version conflicts with Colab's pre-installed PyTorch and CUDA drivers.
- transformers loads and runs the base model.
- peft adds parameter-efficient fine-tuning (LoRA adapters).
- trl provides the SFTTrainer for supervised fine-tuning.
- bitsandbytes enables 4-bit quantization on the NVIDIA GPU.
- datasets loads the CRAVE dataset from Hugging Face Hub.
- accelerate handles device placement and mixed precision.
- scikit-learn provides evaluation metrics (accuracy, F1, confusion matrix).
The install takes about 1-2 minutes. You will see a long output of package downloads.
- After the install completes, log into Hugging Face Hub by running this in a new cell:
import huggingface_hub
# Log in with your write-access token to push models later
huggingface_hub.login()
What happens when you run this?
A text input field appears in the cell output asking for your Hugging Face token. Paste your write-access token and press Enter. This authenticates your session so you can push your trained model to the Hub later.
If you do not have a write-access token yet, go to huggingface.co/settings/tokens and create one with Write permissions.
- Paste your write-access token into the prompt and press Enter.
- Confirm you see a success message indicating you are logged in.
Token rejected or login failed?
Make sure your token has Write access, not just Read. You need write access to push your trained adapter to the Hub in a later step.
If you accidentally pasted incorrectly, re-run the cell to get a fresh prompt.
help me fix my Hugging Face Hub login in Google Colab
Import modules and verify versions
With everything installed and authenticated, you need to import the modules you will use throughout the notebook. Printing versions confirms all libraries loaded correctly and are compatible.
- Import all required modules and print their versions by running this in a new cell:
import torch
import transformers
import datasets
import peft
import trl
import bitsandbytes
import accelerate
import sklearn
# Verify GPU is accessible from PyTorch
print(f"PyTorch version: {torch.__version__}")
print(f"CUDA available: {torch.cuda.is_available()}")
print(f"GPU: {torch.cuda.get_device_name(0)}")
print()
# Print library versions for reproducibility
print(f"transformers: {transformers.__version__}")
print(f"datasets: {datasets.__version__}")
print(f"peft: {peft.__version__}")
print(f"trl: {trl.__version__}")
print(f"bitsandbytes: {bitsandbytes.__version__}")
print(f"accelerate: {accelerate.__version__}")
print(f"scikit-learn: {sklearn.__version__}")
What does this code do?
This cell does two things. First, it confirms PyTorch can see your T4 GPU via CUDA. Second, it prints every library version so you have a reproducibility record.
If any import fails, the install cell did not complete properly. Re-run Cell 2 and then restart the runtime before trying again.
- Run the cell and confirm the output shows CUDA available: True and GPU: Tesla T4.
- Confirm all library version lines print without errors.
Seeing an ImportError?
If a library fails to import, the install may not have completed. Go to Runtime and click Restart session, then re-run Cell 2 (the pip install) followed by Cell 3 (login) and Cell 4 (imports).
If bitsandbytes specifically fails, confirm your runtime is set to T4 GPU. The bitsandbytes library requires an NVIDIA GPU with CUDA.
help me fix an ImportError in my Colab QLoRA setup
✔️ Awesome, I've got everything!
Great. Make sure your notebook is saved. You have 4 cells so far: GPU check, pip install, Hugging Face login, and imports.
ⓧ I'd like to double check the full code
Here is what each cell in your notebook should contain at this point:
# Cell 1: Verify T4 GPU
!nvidia-smi
# Cell 2: Install ML libraries
!pip install -U transformers peft trl bitsandbytes datasets accelerate scikit-learn
# Cell 3: Authenticate with Hugging Face Hub
import huggingface_hub
# Log in with your write-access token to push models later
huggingface_hub.login()
# Cell 4: Import all modules and verify versions
import torch
import transformers
import datasets
import peft
import trl
import bitsandbytes
import accelerate
import sklearn
# Verify GPU is accessible from PyTorch
print(f"PyTorch version: {torch.__version__}")
print(f"CUDA available: {torch.cuda.is_available()}")
print(f"GPU: {torch.cuda.get_device_name(0)}")
print()
# Print library versions for reproducibility
print(f"transformers: {transformers.__version__}")
print(f"datasets: {datasets.__version__}")
print(f"peft: {peft.__version__}")
print(f"trl: {trl.__version__}")
print(f"bitsandbytes: {bitsandbytes.__version__}")
print(f"accelerate: {accelerate.__version__}")
print(f"scikit-learn: {sklearn.__version__}")
Your environment is ready. Next up, you will load the CRAVE dataset and format it into training prompts for the model.
Load and Format the CRAVE Dataset
Your environment is ready and authenticated. Now you need training data.
For a model to learn what good and bad code reviews look like, it needs real examples of both. The CRAVE dataset contains 1,200 human-curated pull request diffs from 123 repositories, each labelled as APPROVE or REQUEST_CHANGES. In this step, you will load the dataset, build a prompt formatter that converts raw PR data into training examples, and verify that everything fits within memory limits.
In this step, get ready to:
- Load and explore the CRAVE dataset structure and label distribution.
- Create a formatting function that converts each sample into a chat-template training prompt.
- Split the data and verify token lengths fit your training budget.
Load the CRAVE dataset and explore its structure
The CRAVE dataset lives on Hugging Face Hub and can be loaded with a single function call. You will inspect the fields and confirm the data is balanced before proceeding.
- Create a new code cell in your Colab notebook and load the dataset by running this code:
from datasets import load_dataset
# Load the CRAVE code review dataset from Hugging Face
dataset = load_dataset("TuringEnterprises/CRAVE")
# Print the dataset structure
print("Dataset structure:")
print(dataset)
# Inspect column names
print("\nColumn names:", dataset['train'].column_names)
# Look at a single sample to understand the data
sample = dataset['train'][0]
print("\nSample entry:")
print(f" pull_request_title: {sample['pull_request_title'][:80]}")
print(f" description: {str(sample['description'])[:80]}")
print(f" patch (first 100 chars): {sample['patch'][:100]}")
print(f" label: {sample['label']}")
print(f" explanation: {sample['explanation'][:80]}")
# Check label distribution across the full dataset
from collections import Counter
all_labels = dataset['train']['label']
print(f"\nTotal samples: {len(all_labels)}")
print(f"Label distribution: {Counter(all_labels)}")
What does this code do?
- load_dataset("TuringEnterprises/CRAVE") downloads the dataset from Hugging Face and returns a DatasetDict. By default, all 1,200 samples are in the train split.
- The key fields are patch (the code diff), label (APPROVE or REQUEST_CHANGES), pull_request_title, description, and explanation.
- The Counter confirms the dataset is perfectly balanced: 600 APPROVE and 600 REQUEST_CHANGES.
- Run the cell. You should see the dataset structure printed, a sample entry with real PR data, and a label distribution showing 600 of each class.
Seeing a connection error?
- Make sure your Colab runtime is connected (green checkmark in the top right). If it disconnected, click **Reconnect**.
- If you see a 404 error, double-check the dataset ID is exactly TuringEnterprises/CRAVE with correct capitalization.
Help me debug a dataset loading error in my Colab notebook.
Create the prompt formatting function
For supervised fine-tuning, you need to convert each raw dataset sample into the chat format the model expects. The Qwen2.5 model uses a system/user/assistant message structure. Your formatting function builds the user prompt from the PR metadata and sets the expected assistant response to the correct label.
- Create a new code cell and define the formatting function by running this code:
from transformers import AutoTokenizer
# Load the tokenizer for prompt formatting and truncation
tokenizer = AutoTokenizer.from_pretrained("Qwen/Qwen2.5-Coder-7B-Instruct")
# Set pad token to eos token (required for training)
tokenizer.pad_token = tokenizer.eos_token
def format_prompt(sample):
"""Convert a CRAVE dataset sample into a chat-template training prompt."""
# Truncate patch if longer than 2048 tokens to keep memory manageable
patch_tokens = tokenizer.encode(sample['patch'], add_special_tokens=False)
if len(patch_tokens) > 2048:
patch = tokenizer.decode(patch_tokens[:2048])
else:
patch = sample['patch']
# Build the system message defining the task
system_message = (
"You are a code review assistant. Given a pull request diff, "
"classify it as APPROVE or REQUEST_CHANGES."
)
# Build the user message with PR context
user_message = (
f"Review the following pull request:\n\n"
f"**Title:** {sample['pull_request_title']}\n\n"
f"**Description:** {sample['description']}\n\n"
f"**Diff:**\n```\n{patch}\n```\n\n"
f"Based on the code changes, should this PR be approved or "
f"should changes be requested? Respond with only APPROVE or REQUEST_CHANGES."
)
# Structure as chat messages with the label as the assistant response
messages = [
{"role": "system", "content": system_message},
{"role": "user", "content": user_message},
{"role": "assistant", "content": sample['label']}
]
# Apply the model's chat template to produce the final training string
return tokenizer.apply_chat_template(messages, tokenize=False)
# Test the formatting function on a sample
sample_prompt = format_prompt(dataset['train'][0])
print("Formatted prompt preview (first 500 chars):")
print(sample_prompt[:500])
print(f"\n... (total length: {len(sample_prompt)} characters)")
What does this code do?
- The tokenizer is loaded from Qwen/Qwen2.5-Coder-7B-Instruct so you can apply truncation and the model's chat template.
- format_prompt truncates long patches to 2048 tokens, preventing out-of-memory errors during training.
- The function structures each sample as a three-message conversation: system (task definition), user (the PR data), and assistant (the correct verdict).
- apply_chat_template converts the messages list into the exact token format Qwen expects, including special tokens like <|im_start|> and <|im_end|>.
- Run the cell. You should see a preview of the formatted prompt showing the chat template structure with the system, user, and assistant messages.
Seeing a tokenizer download error?
- Make sure Cell 3 ran successfully and you are logged into Hugging Face Hub. The tokenizer download requires authentication.
- If you see a model-not-found error, verify the model ID is exactly Qwen/Qwen2.5-Coder-7B-Instruct.
Help me fix a tokenizer loading error in Google Colab.
Split the dataset and verify token lengths
The CRAVE dataset provides pre-defined splits based on repository hash. This prevents data leakage by ensuring code from the same repository does not appear in both training and testing. You will load these splits and tokenize a sample to confirm it fits within the training sequence length.
- Create a new code cell and set up your train/test split by running this code:
# Use the pre-defined dataset splits
train_dataset = load_dataset("TuringEnterprises/CRAVE", split="train")
test_dataset = load_dataset("TuringEnterprises/CRAVE", split="test")
print(f"Train samples: {len(train_dataset)}")
print(f"Test samples: {len(test_dataset)}")
# Tokenize a sample prompt to check its token length
sample_formatted = format_prompt(train_dataset[0])
sample_tokens = tokenizer.encode(sample_formatted)
print(f"\nSample prompt token length: {len(sample_tokens)}")
print(f"Training max_seq_length will be: 2048 tokens")
# Check a few more samples to see the range
lengths = []
for i in range(min(50, len(train_dataset))):
formatted = format_prompt(train_dataset[i])
tokens = tokenizer.encode(formatted)
lengths.append(len(tokens))
print(f"\nToken length stats (first 50 samples):")
print(f" Min: {min(lengths)}")
print(f" Max: {max(lengths)}")
print(f" Mean: {sum(lengths) // len(lengths)}")
What does this code do?
- The pre-defined splits give you approximately 960 training samples and 120 test samples, split by repository hash to prevent leakage.
- Tokenizing a sample confirms that your formatted prompts (with patch truncation at 2048 tokens) fit within the training sequence length you will set in Step 4.
- The stats across 50 samples give you a sense of the token length distribution. Most prompts should be well under 2048 tokens thanks to the truncation in format_prompt.
- Run the cell. You should see approximately 960 train samples and 120 test samples, plus token length statistics confirming prompts fit under the 2048-token training budget.
Seeing different sample counts?
- The pre-defined splits are approximately 960/120. Minor variations are expected due to repository-based splitting.
- If you see 1,200 samples in both, you may have loaded the default split both times. Make sure you pass split="train" and split="test" separately in the load_dataset calls.
Help me understand why my dataset split counts look wrong.
✔️ Awesome, I've got everything!
Great. Double check you have run all three cells (5, 6, and 7) without errors before moving on.
ⓧ I'd like to double check the full code
Here are the three cells you should have added in this step. Compare against your notebook:
# --- Cell 5 ---
from datasets import load_dataset
# Load the CRAVE code review dataset from Hugging Face
dataset = load_dataset("TuringEnterprises/CRAVE")
# Print the dataset structure
print("Dataset structure:")
print(dataset)
# Inspect column names
print("\nColumn names:", dataset['train'].column_names)
# Look at a single sample to understand the data
sample = dataset['train'][0]
print("\nSample entry:")
print(f" pull_request_title: {sample['pull_request_title'][:80]}")
print(f" description: {str(sample['description'])[:80]}")
print(f" patch (first 100 chars): {sample['patch'][:100]}")
print(f" label: {sample['label']}")
print(f" explanation: {sample['explanation'][:80]}")
# Check label distribution across the full dataset
from collections import Counter
all_labels = dataset['train']['label']
print(f"\nTotal samples: {len(all_labels)}")
print(f"Label distribution: {Counter(all_labels)}")
# --- Cell 6 ---
from transformers import AutoTokenizer
# Load the tokenizer for prompt formatting and truncation
tokenizer = AutoTokenizer.from_pretrained("Qwen/Qwen2.5-Coder-7B-Instruct")
# Set pad token to eos token (required for training)
tokenizer.pad_token = tokenizer.eos_token
def format_prompt(sample):
"""Convert a CRAVE dataset sample into a chat-template training prompt."""
# Truncate patch if longer than 2048 tokens to keep memory manageable
patch_tokens = tokenizer.encode(sample['patch'], add_special_tokens=False)
if len(patch_tokens) > 2048:
patch = tokenizer.decode(patch_tokens[:2048])
else:
patch = sample['patch']
# Build the system message defining the task
system_message = (
"You are a code review assistant. Given a pull request diff, "
"classify it as APPROVE or REQUEST_CHANGES."
)
# Build the user message with PR context
user_message = (
f"Review the following pull request:\n\n"
f"**Title:** {sample['pull_request_title']}\n\n"
f"**Description:** {sample['description']}\n\n"
f"**Diff:**\n```\n{patch}\n```\n\n"
f"Based on the code changes, should this PR be approved or "
f"should changes be requested? Respond with only APPROVE or REQUEST_CHANGES."
)
# Structure as chat messages with the label as the assistant response
messages = [
{"role": "system", "content": system_message},
{"role": "user", "content": user_message},
{"role": "assistant", "content": sample['label']}
]
# Apply the model's chat template to produce the final training string
return tokenizer.apply_chat_template(messages, tokenize=False)
# Test the formatting function on a sample
sample_prompt = format_prompt(dataset['train'][0])
print("Formatted prompt preview (first 500 chars):")
print(sample_prompt[:500])
print(f"\n... (total length: {len(sample_prompt)} characters)")
# --- Cell 7 ---
# Use the pre-defined dataset splits
train_dataset = load_dataset("TuringEnterprises/CRAVE", split="train")
test_dataset = load_dataset("TuringEnterprises/CRAVE", split="test")
print(f"Train samples: {len(train_dataset)}")
print(f"Test samples: {len(test_dataset)}")
# Tokenize a sample prompt to check its token length
sample_formatted = format_prompt(train_dataset[0])
sample_tokens = tokenizer.encode(sample_formatted)
print(f"\nSample prompt token length: {len(sample_tokens)}")
print(f"Training max_seq_length will be: 2048 tokens")
# Check a few more samples to see the range
lengths = []
for i in range(min(50, len(train_dataset))):
formatted = format_prompt(train_dataset[i])
tokens = tokenizer.encode(formatted)
lengths.append(len(tokens))
print(f"\nToken length stats (first 50 samples):")
print(f" Min: {min(lengths)}")
print(f" Max: {max(lengths)}")
print(f" Mean: {sum(lengths) // len(lengths)}")
Your data pipeline is ready. You have 960 labelled training examples formatted as chat prompts, each verified to fit within memory. Next up, you will load the actual model in 4-bit precision and configure the LoRA adapters for efficient training.
Configure QLoRA and Load the Model
Your dataset is formatted and split. Now you need the actual model to fine-tune.
Qwen2.5-Coder-7B-Instruct has 7.6 billion parameters, which at full precision would need ~15 GB of memory just for the weights. That exceeds your T4 GPU's 16 GB budget before you even start training. QLoRA solves this by compressing the model to 4-bit precision and only training a tiny set of adapter parameters on top.
In this step, get ready to:
- Define a 4-bit quantization config and load the model onto your GPU.
- Apply LoRA adapters so only ~1% of parameters are trainable.
Define the quantization config and load the model
4-bit quantization compresses each model weight from 16 bits down to 4 bits. This cuts memory usage by roughly 75%, making a 7B model fit comfortably on a free Colab T4.
- Create a new code cell (Cell 8) and define the BitsAndBytesConfig by pasting this code:
import torch
from transformers import BitsAndBytesConfig
# Configure 4-bit quantization using NF4 data type
bnb_config = BitsAndBytesConfig(
load_in_4bit=True,
bnb_4bit_quant_type='nf4',
bnb_4bit_use_double_quant=True,
bnb_4bit_compute_dtype=torch.bfloat16
)
print('Quantization config ready:', bnb_config.quant_method)
What does this code do?
This config tells Hugging Face how to compress the model when loading it.
- load_in_4bit=True enables 4-bit quantization instead of loading at full 16-bit precision.
- bnb_4bit_quant_type='nf4' uses the NormalFloat4 data type, which is optimized for normally-distributed neural network weights.
- bnb_4bit_use_double_quant=True applies a second round of quantization to the quantization constants themselves, saving additional memory.
- bnb_4bit_compute_dtype=torch.bfloat16 uses bfloat16 for actual matrix math, balancing speed and numerical stability.
- Run the cell. You should see output confirming the quantization config is ready.
Now you can load the model with this config applied. The model download is approximately 4-5 GB, so this cell takes a few minutes to complete.
- Create a new code cell (Cell 9) and load the model and tokenizer by pasting this code:
from transformers import AutoModelForCausalLM, AutoTokenizer
# Load the model with 4-bit quantization applied
model = AutoModelForCausalLM.from_pretrained(
'Qwen/Qwen2.5-Coder-7B-Instruct',
quantization_config=bnb_config,
device_map='auto'
)
# Load the tokenizer and set pad token
tokenizer = AutoTokenizer.from_pretrained('Qwen/Qwen2.5-Coder-7B-Instruct')
tokenizer.pad_token = tokenizer.eos_token
print('Model loaded on:', model.device)
print('Pad token set to:', tokenizer.pad_token)
What does this code do?
quantization_config=bnb_config applies your 4-bit config during loading, compressing the model from ~15 GB down to ~4.5 GB.
device_map='auto' lets Hugging Face automatically place model layers on your T4 GPU. The tokenizer converts text to numbers and back. Setting pad_token prevents errors during batched training, since Qwen's tokenizer does not define one by default.
- Run the cell. This downloads and quantizes the model, which takes 2-5 minutes depending on network speed.
- You should see output showing the model is loaded on cuda:0 and the pad token is set.
Model download stalling or out of memory?
- If the download is slow, Colab's connection may have throttled. Go to Runtime > Disconnect and delete runtime, reconnect, and re-run all cells from the top.
- If you see an out-of-memory error, make sure you selected the T4 GPU runtime (not CPU) in Runtime > Change runtime type.
- help me debug a model loading error in Google Colab
Apply LoRA adapters to the quantized model
LoRA (Low-Rank Adaptation) freezes the original model weights and injects small trainable matrices into specific layers. Instead of updating 7.6 billion parameters, you only train a few million adapter parameters.
- Create a new code cell (Cell 10) and configure LoRA by pasting this code:
from peft import prepare_model_for_kbit_training, LoraConfig, get_peft_model
# Prepare the quantized model for training (enables gradient checkpointing)
model = prepare_model_for_kbit_training(model)
# Define which layers get LoRA adapters and their size
lora_config = LoraConfig(
r=16,
lora_alpha=32,
target_modules=['q_proj', 'k_proj', 'v_proj', 'o_proj', 'gate_proj', 'up_proj', 'down_proj'],
lora_dropout=0.05,
bias='none',
task_type='CAUSAL_LM'
)
# Apply LoRA adapters to the model
model = get_peft_model(model, lora_config)
# Show how many parameters are actually trainable
model.print_trainable_parameters()
What does this code do?
prepare_model_for_kbit_training() enables gradient computation on the quantized model so backpropagation works correctly.
- r=16 sets the rank of the LoRA matrices. Higher rank means more trainable parameters and capacity, but also more memory.
- lora_alpha=32 is a scaling factor that controls how strongly the adapter weights influence the output. A common rule of thumb is alpha = 2x rank.
- target_modules lists the layers receiving adapters. These cover all attention projections (q, k, v, o) and MLP projections (gate, up, down) in each transformer block.
- task_type='CAUSAL_LM' tells PEFT this is a text generation model, so it configures the adapters accordingly.
get_peft_model() wraps your frozen model with the LoRA adapters. Only the adapter weights will be updated during training.
- Run the cell. You should see output similar to:
trainable params: X,XXX,XXX || all params: 7,XXX,XXX,XXX || trainable%: 0.XX
The trainable percentage should be well under 1%. This confirms that LoRA is working correctly, only training a tiny fraction of the model.
Seeing an error about CUDA or dtype?
- If you see a CUDA-related error, your runtime may have disconnected. Check that the GPU is still active by running !nvidia-smi in a new cell.
- If you see a dtype mismatch error, make sure you ran the BitsAndBytesConfig cell (Cell 8) before loading the model.
- help me fix a PEFT or LoRA error in my Colab notebook
Why target all seven projection layers?
Some tutorials only target the attention layers (q_proj, v_proj). Targeting all seven projections gives the adapter more capacity to learn task-specific patterns, which matters for classification tasks where both attention and feed-forward layers contribute to the decision.
The tradeoff is slightly more trainable parameters and memory usage. With rank 16 across seven modules, you still stay well under 1% of total parameters.
✔️ Awesome, I've got everything!
Great. Double check you have run all three cells (8, 9, and 10) and the trainable parameter percentage is under 1%.
ⓧ I'd like to double check the full code
Here are all three cells you added in this step:
Cell 8:
import torch
from transformers import BitsAndBytesConfig
# Configure 4-bit quantization using NF4 data type
bnb_config = BitsAndBytesConfig(
load_in_4bit=True,
bnb_4bit_quant_type='nf4',
bnb_4bit_use_double_quant=True,
bnb_4bit_compute_dtype=torch.bfloat16
)
print('Quantization config ready:', bnb_config.quant_method)
Cell 9:
from transformers import AutoModelForCausalLM, AutoTokenizer
# Load the model with 4-bit quantization applied
model = AutoModelForCausalLM.from_pretrained(
'Qwen/Qwen2.5-Coder-7B-Instruct',
quantization_config=bnb_config,
device_map='auto'
)
# Load the tokenizer and set pad token
tokenizer = AutoTokenizer.from_pretrained('Qwen/Qwen2.5-Coder-7B-Instruct')
tokenizer.pad_token = tokenizer.eos_token
print('Model loaded on:', model.device)
print('Pad token set to:', tokenizer.pad_token)
Cell 10:
from peft import prepare_model_for_kbit_training, LoraConfig, get_peft_model
# Prepare the quantized model for training (enables gradient checkpointing)
model = prepare_model_for_kbit_training(model)
# Define which layers get LoRA adapters and their size
lora_config = LoraConfig(
r=16,
lora_alpha=32,
target_modules=['q_proj', 'k_proj', 'v_proj', 'o_proj', 'gate_proj', 'up_proj', 'down_proj'],
lora_dropout=0.05,
bias='none',
task_type='CAUSAL_LM'
)
# Apply LoRA adapters to the model
model = get_peft_model(model, lora_config)
# Show how many parameters are actually trainable
model.print_trainable_parameters()
Your model is quantized to 4-bit, wrapped with LoRA adapters, and ready to learn. Next up, you will configure the training loop and fine-tune on the CRAVE dataset.
Train the Model
Your model is configured and ready to learn. The LoRA adapter is attached, the dataset is formatted, and the quantized model fits comfortably in GPU memory.
Now you need to define the training schedule and let the trainer run. Once training completes, you will save and publish the adapter to Hugging Face Hub so it persists beyond this Colab session.
In this step, get ready to:
- Define training hyperparameters with SFTConfig.
- Run the training loop and monitor loss.
- Save and push the trained adapter to Hugging Face Hub.
Define the training arguments
The training configuration controls how your model learns from the data. You need to balance speed, memory usage, and training quality.
- Create a new code cell (Cell 11) and define the training arguments by pasting this code:
from trl import SFTConfig, SFTTrainer
# Define training hyperparameters
training_args = SFTConfig(
output_dir='./results',
num_train_epochs=2,
per_device_train_batch_size=2,
gradient_accumulation_steps=4,
learning_rate=2e-4,
lr_scheduler_type='cosine',
logging_steps=10,
gradient_checkpointing=True,
max_seq_length=2048
)
What does this code do?
Each parameter shapes how the model trains:
- num_train_epochs=2 passes through the full 960-sample training set twice.
- per_device_train_batch_size=2 with gradient_accumulation_steps=4 gives an effective batch size of 8. This keeps memory low while still producing stable gradient updates.
- learning_rate=2e-4 is the standard rate for LoRA adapters, about 10x higher than full fine-tuning because only the small adapter weights are updating.
- lr_scheduler_type='cosine' gradually reduces the learning rate in a smooth curve, helping the model converge without overshooting.
- gradient_checkpointing=True trades compute time for memory savings by recomputing activations during the backward pass instead of storing them all.
- max_seq_length=2048 truncates any prompt longer than 2048 tokens, keeping memory usage predictable.
Why cosine scheduling?
A cosine scheduler starts at the full learning rate and gradually reduces it in a smooth curve toward zero. This lets the model make large updates early (when it has the most to learn) and fine-tune with smaller adjustments at the end.
- Run the cell. You should see no output if everything compiles correctly.
Seeing a TypeError about max_seq_length?
In newer versions of TRL (v0.20+), the parameter was renamed from max_seq_length to max_length. If you see this error, change max_seq_length=2048 to max_length=2048 in your SFTConfig.
Help me fix a TypeError in my SFTConfig training arguments.
Start training
With the hyperparameters set, you can now instantiate the trainer and kick off the training loop. The trainer handles tokenization, batching, gradient updates, and logging automatically.
- Create a new code cell (Cell 12) and start training by pasting this code:
# Instantiate the trainer and start fine-tuning
trainer = SFTTrainer(
model=model,
train_dataset=train_dataset,
tokenizer=tokenizer,
peft_config=lora_config,
args=training_args
)
# Launch training
trainer.train()
What does this code do?
SFTTrainer wraps your model, dataset, and training configuration into a single object that manages the full training loop.
- peft_config=lora_config tells the trainer which LoRA configuration to use for parameter-efficient training.
- trainer.train() starts the training loop. You will see a progress bar and loss values logged every 10 steps.
- Run the cell. Training takes approximately 15-20 minutes on the T4 GPU.
- Watch the loss values in the output. You should see the training loss decrease over time, starting above 1.0 and gradually dropping.
Monitor the loss curve
A healthy training run shows the loss dropping steadily in the first few dozen steps, then flattening out toward the end. If the loss stays flat from step 1, something is wrong with your data formatting or model setup.
Training loss not decreasing?
If the loss stays flat or your trainable parameters show 0, the LoRA adapters may have been frozen by the trainer. This is a known issue in TRL v0.21+ when get_peft_model() is called before passing to SFTTrainer. Try restarting the runtime, re-running Cells 1-9 (skipping Cell 10), and passing peft_config=lora_config directly to SFTTrainer without pre-applying it.
Help me debug why my training loss is not decreasing.
Save and push the adapter to Hugging Face Hub
Training is complete. The adapter weights exist only in memory right now. You need to save them locally and push them to Hugging Face Hub so they persist beyond this Colab session.
- Create a new code cell (Cell 13) and save your trained adapter by pasting this code:
# Save the trained adapter locally
trainer.save_model('./code-review-classifier-qlora')
# Push the adapter and tokenizer to Hugging Face Hub
model.push_to_hub('[[HF_USERNAME="your-username"]]/code-review-classifier-qlora')
tokenizer.push_to_hub('[[HF_USERNAME="your-username"]]/code-review-classifier-qlora')
What does this code do?
- trainer.save_model('./code-review-classifier-qlora') writes the LoRA adapter files (not the full 7B model) to a local directory. These files are small, typically under 100 MB.
- model.push_to_hub(...) uploads the adapter weights to your Hugging Face Hub account, creating a new model repository.
- tokenizer.push_to_hub(...) uploads the tokenizer alongside the adapter so anyone loading your model gets the correct tokenizer automatically.
- Run the cell. You should see upload progress bars followed by a URL to your new model repository on Hugging Face Hub.
- Click the URL in the output to verify your model page exists on Hugging Face Hub. You should see the adapter files listed in the repository.
Seeing an authentication error?
Make sure you logged in with a write-access token in Cell 3. Read-only tokens cannot push models. Go to your Hugging Face settings, generate a new token with write permissions, and re-run Cell 3 with the new token.
Help me fix my Hugging Face Hub authentication error.
✔️ Awesome, I've got everything!
Great. Double check you have saved all your cells and that your model page is live on Hugging Face Hub.
ⓧ I'd like to double check the full code
Here are the three cells you added in this step:
Cell 11:
from trl import SFTConfig, SFTTrainer
# Define training hyperparameters
training_args = SFTConfig(
output_dir='./results',
num_train_epochs=2,
per_device_train_batch_size=2,
gradient_accumulation_steps=4,
learning_rate=2e-4,
lr_scheduler_type='cosine',
logging_steps=10,
gradient_checkpointing=True,
max_seq_length=2048
)
Cell 12:
# Instantiate the trainer and start fine-tuning
trainer = SFTTrainer(
model=model,
train_dataset=train_dataset,
tokenizer=tokenizer,
peft_config=lora_config,
args=training_args
)
# Launch training
trainer.train()
Cell 13:
# Save the trained adapter locally
trainer.save_model('./code-review-classifier-qlora')
# Push the adapter and tokenizer to Hugging Face Hub
model.push_to_hub('your-username/code-review-classifier-qlora')
tokenizer.push_to_hub('your-username/code-review-classifier-qlora')
Your adapter is trained and published. Next up, you will evaluate how well it performs on unseen code reviews and document your results in a model card.
Evaluate and Publish Results
Your model is trained and saved on Hugging Face Hub. Training a model is only half the story. Without evaluation, you have no evidence that the model learned anything useful from those 960 PR diffs.
In this step, you will run inference on the held-out test set, compute classification metrics, and publish a model card that documents your results. This is what transforms a trained adapter into a credible portfolio artifact.
In this step, get ready to:
- Run inference on all 120 test samples and extract predicted verdicts.
- Compute accuracy, F1, and a confusion matrix using sklearn.
- Create and push a model card with evaluation results to Hugging Face Hub.
Run inference on the test set
To evaluate a classifier, you need predictions on data the model never saw during training. The test split has 120 samples with known ground truth labels.
- Create a new code cell (Cell 14) and add the inference loop by pasting this code:
import torch
# Store predictions and ground truth
predictions = []
ground_truth = []
# Run inference on each test sample
for sample in test_dataset:
# Format the prompt using our existing function
prompt = format_prompt(sample)
# Tokenize and move to GPU
inputs = tokenizer(prompt, return_tensors="pt", truncation=True, max_length=2048).to(model.device)
input_length = inputs.input_ids.shape[1]
# Generate the model's prediction
with torch.no_grad():
output_ids = model.generate(
**inputs,
max_new_tokens=20,
do_sample=False
)
# Decode only the newly generated tokens
generated_text = tokenizer.decode(output_ids[0, input_length:], skip_special_tokens=True).strip()
# Extract the verdict from the generated text
if "APPROVE" in generated_text:
predicted_label = "APPROVE"
else:
predicted_label = "REQUEST_CHANGES"
predictions.append(predicted_label)
ground_truth.append(sample["label"])
print(f"Inference complete: {len(predictions)} predictions generated")
What does this code do?
This loop processes each test sample one at a time. For each sample, it formats the PR diff into a chat prompt, tokenizes it, and feeds it to the model.
The key technique is slicing the output: output_ids[0, input_length:] strips out the original prompt tokens and keeps only the newly generated text. The model outputs either APPROVE or REQUEST_CHANGES as its verdict.
- Run the cell. This takes a few minutes to process all 120 samples.
- You should see the message Inference complete: 120 predictions generated when it finishes.
Seeing a CUDA out of memory error?
If you hit memory limits during inference, add torch.cuda.empty_cache() at the top of the cell before the loop. The T4 GPU has limited VRAM and the training step may have left cached tensors.
help me fix a CUDA out of memory error during inference in Google Colab
Compute classification metrics
Raw predictions alone do not tell you how well the model performs. Classification metrics give you a structured view of where the model succeeds and where it fails.
- Create a new code cell (Cell 15) and compute the evaluation metrics by pasting this code:
from sklearn.metrics import accuracy_score, precision_score, recall_score, f1_score, confusion_matrix
# Compute core classification metrics
accuracy = accuracy_score(ground_truth, predictions)
precision = precision_score(ground_truth, predictions, pos_label="REQUEST_CHANGES")
recall = recall_score(ground_truth, predictions, pos_label="REQUEST_CHANGES")
f1 = f1_score(ground_truth, predictions, pos_label="REQUEST_CHANGES")
print(f"Accuracy: {accuracy:.4f}")
print(f"Precision: {precision:.4f}")
print(f"Recall: {recall:.4f}")
print(f"F1 Score: {f1:.4f}")
# Print confusion matrix
print("\nConfusion Matrix:")
print(" Predicted APPROVE Predicted REQUEST_CHANGES")
cm = confusion_matrix(ground_truth, predictions, labels=["APPROVE", "REQUEST_CHANGES"])
print(f"Actual APPROVE: {cm[0][0]} {cm[0][1]}")
print(f"Actual REQUEST_CHANGES: {cm[1][0]} {cm[1][1]}")
# Analyze errors
print(f"\nTrue Positives (correct REQUEST_CHANGES): {cm[1][1]}")
print(f"False Positives (wrongly flagged): {cm[0][1]}")
print(f"True Negatives (correct APPROVE): {cm[0][0]}")
print(f"False Negatives (missed issues): {cm[1][0]}")
What do these metrics tell you?
- Accuracy measures overall correctness across both classes.
- Precision tells you how many of the model's REQUEST_CHANGES predictions were actually correct. High precision means fewer false alarms.
- Recall tells you how many actual REQUEST_CHANGES cases the model caught. High recall means fewer missed issues.
- F1 Score is the harmonic mean of precision and recall. It balances both into a single number.
The confusion matrix breaks down exactly which types of errors the model makes. False negatives (missed issues) are typically more dangerous in code review than false positives (over-flagging).
- Run the cell. You should see your accuracy, precision, recall, F1 score, and confusion matrix printed.
- Note down your accuracy and F1 values. You will include them in the model card next.
Seeing very low accuracy?
If accuracy is below 50%, the model may not have converged during training. Check that your training loss decreased over the 2 epochs. Also verify that your format_prompt function produces prompts that clearly ask for APPROVE or REQUEST_CHANGES as the output.
help me debug why my fine-tuned model has very low accuracy on the test set
Create and push the model card
A model card is documentation that accompanies your model on Hugging Face Hub. It tells anyone who finds your model what it does, how it was trained, and how well it performs.
- Create a new code cell (Cell 16) and build the model card by pasting this code:
from huggingface_hub import ModelCard
# Build the model card content as a markdown string
model_card_content = f"""---
license: apache-2.0
tags:
- code-review
- qlora
- peft
datasets:
- TuringEnterprises/CRAVE
metrics:
- accuracy
- f1
base_model: Qwen/Qwen2.5-Coder-7B-Instruct
---
# Code Review Classifier (QLoRA Fine-Tuned)
This model classifies pull request diffs as **APPROVE** or **REQUEST_CHANGES**.
## Model Details
- **Base model:** Qwen/Qwen2.5-Coder-7B-Instruct
- **Method:** QLoRA (4-bit NF4 quantization + LoRA rank 16)
- **Dataset:** [CRAVE](https://huggingface.co/datasets/TuringEnterprises/CRAVE) (960 training samples, 120 test samples)
- **Task:** Binary classification of code review verdicts
## Training Hyperparameters
| Parameter | Value |
|-----------|-------|
| Epochs | 2 |
| Batch size | 2 |
| Gradient accumulation steps | 4 |
| Learning rate | 2e-4 |
| LR scheduler | Cosine |
| LoRA rank | 16 |
| LoRA alpha | 32 |
| LoRA dropout | 0.05 |
| Max sequence length | 2048 |
| Quantization | NF4 (double quantization) |
## Evaluation Results
| Metric | Score |
|--------|-------|
| Accuracy | {accuracy:.4f} |
| Precision | {precision:.4f} |
| Recall | {recall:.4f} |
| F1 Score | {f1:.4f} |
### Confusion Matrix
| | Predicted APPROVE | Predicted REQUEST_CHANGES |
|--|---|---|
| Actual APPROVE | {cm[0][0]} | {cm[0][1]} |
| Actual REQUEST_CHANGES | {cm[1][0]} | {cm[1][1]} |
## Dataset Citation
This model was trained on the CRAVE dataset:
- **Source:** [TuringEnterprises/CRAVE](https://huggingface.co/datasets/TuringEnterprises/CRAVE)
- **Size:** 1,200 samples from 123 repositories
- **License:** MIT
"""
# Create and push the model card
card = ModelCard(model_card_content)
card.push_to_hub("your-username/code-review-classifier-qlora")
print("Model card pushed to Hugging Face Hub!")
What does this code do?
The model card is a markdown file with YAML frontmatter at the top. The YAML metadata tells Hugging Face how to categorize your model (license, tags, base model, metrics). The body documents your training setup and results.
Using f-string formatting, the card automatically includes your actual evaluation metrics. The ModelCard class wraps the markdown string and push_to_hub uploads it as the README.md in your model repository.
- Before running, replace your-username with your actual Hugging Face username (the same one from Step 4).
- Run the cell. You should see the confirmation message Model card pushed to Hugging Face Hub! printed.
- Visit your model page on Hugging Face Hub. The README should now display your model card with evaluation metrics, training hyperparameters, and the confusion matrix.
Model card not showing on the Hub?
Make sure you are logged into Hugging Face (Cell 3 from Step 1) and that the repo name matches exactly what you used in Step 4 when pushing the adapter. The push_to_hub call overwrites the existing README.md in that repo.
help me fix a permissions error when pushing a model card to Hugging Face Hub
✔️ Awesome, I've got everything!
Great. Double check you have saved all your cells and that your Hugging Face model page shows the updated model card.
ⓧ I'd like to double check the full code
Here are the three cells you added in this step:
Cell 14 (inference loop):
import torch
# Store predictions and ground truth
predictions = []
ground_truth = []
# Run inference on each test sample
for sample in test_dataset:
# Format the prompt using our existing function
prompt = format_prompt(sample)
# Tokenize and move to GPU
inputs = tokenizer(prompt, return_tensors="pt", truncation=True, max_length=2048).to(model.device)
input_length = inputs.input_ids.shape[1]
# Generate the model's prediction
with torch.no_grad():
output_ids = model.generate(
**inputs,
max_new_tokens=20,
do_sample=False
)
# Decode only the newly generated tokens
generated_text = tokenizer.decode(output_ids[0, input_length:], skip_special_tokens=True).strip()
# Extract the verdict from the generated text
if "APPROVE" in generated_text:
predicted_label = "APPROVE"
else:
predicted_label = "REQUEST_CHANGES"
predictions.append(predicted_label)
ground_truth.append(sample["label"])
print(f"Inference complete: {len(predictions)} predictions generated")
Cell 15 (evaluation metrics):
from sklearn.metrics import accuracy_score, precision_score, recall_score, f1_score, confusion_matrix
# Compute core classification metrics
accuracy = accuracy_score(ground_truth, predictions)
precision = precision_score(ground_truth, predictions, pos_label="REQUEST_CHANGES")
recall = recall_score(ground_truth, predictions, pos_label="REQUEST_CHANGES")
f1 = f1_score(ground_truth, predictions, pos_label="REQUEST_CHANGES")
print(f"Accuracy: {accuracy:.4f}")
print(f"Precision: {precision:.4f}")
print(f"Recall: {recall:.4f}")
print(f"F1 Score: {f1:.4f}")
# Print confusion matrix
print("\nConfusion Matrix:")
print(" Predicted APPROVE Predicted REQUEST_CHANGES")
cm = confusion_matrix(ground_truth, predictions, labels=["APPROVE", "REQUEST_CHANGES"])
print(f"Actual APPROVE: {cm[0][0]} {cm[0][1]}")
print(f"Actual REQUEST_CHANGES: {cm[1][0]} {cm[1][1]}")
# Analyze errors
print(f"\nTrue Positives (correct REQUEST_CHANGES): {cm[1][1]}")
print(f"False Positives (wrongly flagged): {cm[0][1]}")
print(f"True Negatives (correct APPROVE): {cm[0][0]}")
print(f"False Negatives (missed issues): {cm[1][0]}")
Cell 16 (model card):
from huggingface_hub import ModelCard
# Build the model card content as a markdown string
model_card_content = f"""---
license: apache-2.0
tags:
- code-review
- qlora
- peft
datasets:
- TuringEnterprises/CRAVE
metrics:
- accuracy
- f1
base_model: Qwen/Qwen2.5-Coder-7B-Instruct
---
# Code Review Classifier (QLoRA Fine-Tuned)
This model classifies pull request diffs as **APPROVE** or **REQUEST_CHANGES**.
## Model Details
- **Base model:** Qwen/Qwen2.5-Coder-7B-Instruct
- **Method:** QLoRA (4-bit NF4 quantization + LoRA rank 16)
- **Dataset:** [CRAVE](https://huggingface.co/datasets/TuringEnterprises/CRAVE) (960 training samples, 120 test samples)
- **Task:** Binary classification of code review verdicts
## Training Hyperparameters
| Parameter | Value |
|-----------|-------|
| Epochs | 2 |
| Batch size | 2 |
| Gradient accumulation steps | 4 |
| Learning rate | 2e-4 |
| LR scheduler | Cosine |
| LoRA rank | 16 |
| LoRA alpha | 32 |
| LoRA dropout | 0.05 |
| Max sequence length | 2048 |
| Quantization | NF4 (double quantization) |
## Evaluation Results
| Metric | Score |
|--------|-------|
| Accuracy | {accuracy:.4f} |
| Precision | {precision:.4f} |
| Recall | {recall:.4f} |
| F1 Score | {f1:.4f} |
### Confusion Matrix
| | Predicted APPROVE | Predicted REQUEST_CHANGES |
|--|---|---|
| Actual APPROVE | {cm[0][0]} | {cm[0][1]} |
| Actual REQUEST_CHANGES | {cm[1][0]} | {cm[1][1]} |
## Dataset Citation
This model was trained on the CRAVE dataset:
- **Source:** [TuringEnterprises/CRAVE](https://huggingface.co/datasets/TuringEnterprises/CRAVE)
- **Size:** 1,200 samples from 123 repositories
- **License:** MIT
"""
# Create and push the model card
card = ModelCard(model_card_content)
card.push_to_hub("your-username/code-review-classifier-qlora")
print("Model card pushed to Hugging Face Hub!")
Your fine-tuned model is now evaluated, documented, and published. You have a live Hugging Face model card that proves your ML engineering skills to anyone who visits the page. Next up, the Secret Mission takes this further with a shareable demo.
Secret mission
Build a Gradio Demo
Your fine-tuned model works great in a notebook. But what if anyone could paste a code diff and get an instant verdict, without touching Python or knowing what QLoRA is? Build a shareable web app powered by your model.
Clean Up Your Resources
Clean Up Your Resources
This project runs entirely on Google Colab's free tier and Hugging Face's free storage. There are no ongoing costs. Decide whether to keep your resources available, pause them to come back later, or delete them entirely.
Resources you used:
- Google Colab notebook (GPU runtime and notebook file stored in Google Drive).
- Hugging Face Hub model repository (your trained LoRA adapter at your-username/code-review-classifier-qlora).
Keep everything running
No action needed. Choose this if you plan to continue experimenting with your fine-tuned model or want to share the Hugging Face repo as a portfolio piece.
- Your Colab runtime will disconnect automatically after a period of inactivity. The notebook file remains in Google Drive.
- Your Hugging Face Hub model repository stays public and accessible to anyone.
- The Gradio public URL expires automatically when the Colab runtime disconnects.
Pause - I'll come back to this later
Free up the GPU runtime now but keep your notebook and model for later use.
- In Google Colab, click Runtime in the top menu bar.
- Select Disconnect and delete runtime. This releases the T4 GPU but keeps your notebook saved in Google Drive.
- Your Hugging Face Hub model repository stays online. No action needed there.
- When you return, open the notebook from Google Drive and reconnect to a GPU runtime to resume.
Delete - I don't want to use this again
Remove all project resources permanently. This cannot be undone.
- In Google Colab, click Runtime in the top menu, then select Disconnect and delete runtime.
- Open Google Drive and locate your notebook file (the .ipynb file).
- Right-click the notebook and select Remove to move it to Trash. Empty Trash to permanently delete.
- To delete your Hugging Face Hub model repository, open your model page at huggingface.co/your-username/code-review-classifier-qlora.
- Click the Settings tab on the model page.
- Scroll to the bottom and click Delete this repository. Confirm the deletion when prompted. This is irreversible.
Nice Work!
Nice Work!
You just fine-tuned a 7-billion parameter code model to classify pull request diffs as APPROVE or REQUEST_CHANGES. That is a real ML engineering pipeline from data prep to published model.
You've learned how to:
- Fine-tune Qwen2.5-Coder-7B-Instruct on 960 real PR diffs using QLoRA with 4-bit quantization, fitting a 7B model into a free T4 GPU.
- Evaluate your classifier with accuracy, F1, and a confusion matrix on a held-out test set of 120 PRs to measure real-world performance.
- Publish a trained LoRA adapter to Hugging Face Hub with a complete model card documenting your training approach and results.
- Secret Mission: Built a shareable Gradio demo that lets anyone paste a code diff and get an instant verdict from your fine-tuned model.
Ready to quiz yourself?