Fine-Tune a Course Advisor Chatbot
Fine-tune a 1.5B LLM with QLoRA to answer domain-specific course questions.
Introduction
30 Second Summary
Every organisation has unique knowledge that generic AI models simply don't have. Whether it's course catalogues, internal policies, or product details, a base model will hallucinate or give vague answers when asked about your specific domain.
In this project, you will fine-tune a language model to become a knowledgeable course advisor for Corvit Systems, a Pakistani IT training institute. You'll teach the model accurate information about Corvit's courses, campuses, and programs using QLoRA on a free GPU.
What You'll Build
You'll ask your chatbot questions like "What AI courses do you offer?" and watch it respond with accurate, specific details about real courses, durations, and delivery modes.
By the end of this project, you'll have:
- A working fine-tuned chatbot that answers questions about Corvit's tech courses (AI, Cyber Security, Web Development, Cloud Computing, DevOps) with accurate, specific information.
- A clear before/after comparison showing the base model producing generic nonsense about Corvit, while your fine-tuned version responds like a knowledgeable advisor.
- A portable LoRA adapter file (~50 MB) that customizes the base model's behaviour without modifying the original weights.
- Secret Mission: Push your trained adapter to Hugging Face Hub and handle tricky edge-case questions the model has never seen.
Are there any prerequisites?
You'll need basic Python familiarity, a Google account (for Google Colab), and a free Hugging Face account. Everything runs in the browser. No local GPU or Python install required.
Before We Start
Before we dive in, let's lock in what you're about to build and why it matters.
Set Up Your Colab Environment
Fine-tuning a language model means updating millions of parameters across thousands of training examples. This is computationally intensive work that needs a dedicated GPU.
Google Colab provides a free T4 GPU with ~15 GB of VRAM. In this step, you'll set up a Colab notebook with GPU acceleration and install Unsloth, the library that makes QLoRA fine-tuning fast and memory-efficient on a single GPU.
In this step, get ready to:
- Enable a T4 GPU runtime in Google Colab.
- Install Unsloth and supporting libraries.
- Verify the GPU is accessible and ready for training.
Create a new Colab notebook and enable GPU
Google Colab is a free browser-based notebook environment that gives you access to GPUs without installing anything locally.
- Open your browser and navigate to colab.research.google.com.
- Sign in with your Google account if prompted.
- Click New notebook to create a fresh notebook.
Now you need to switch the runtime to use a T4 GPU instead of the default CPU.
- In the top menu bar, click Runtime.
- Select Change runtime type.
- In the Hardware accelerator dropdown, select T4 GPU.
- Click Save.
- Click the Connect button in the top-right corner of the notebook.
After a few seconds, you should see a green checkmark and RAM/Disk usage indicators in the top-right corner, confirming your notebook is connected to a GPU runtime.
Don't see T4 GPU as an option?
If T4 GPU is not listed, try selecting any available GPU option. Colab's free tier rotates GPU availability based on demand and your usage history.
If no GPU options appear at all, wait a few minutes and try again. Colab limits GPU access for accounts that have used a lot of GPU time recently.
help me get a GPU runtime in Google Colab
Install Unsloth and dependencies
Unsloth is a library that makes fine-tuning up to 2x faster while using 60% less memory than standard approaches. You also need the datasets library for formatting training data and trl for the training loop.
- Install Unsloth and the training libraries by running this code in a new cell:
!pip install unsloth
!pip install datasets trl
What do these libraries do?
- unsloth provides optimised model loading and LoRA training that runs efficiently on a single T4 GPU.
- datasets is Hugging Face's library for creating and managing training datasets.
- trl (Transformer Reinforcement Learning) provides SFTTrainer, the training loop you'll use for supervised fine-tuning.
The installation takes 1-2 minutes. You'll see a stream of output as packages download and install.
Seeing installation errors?
If you see dependency conflict warnings, these are usually safe to ignore as long as the install completes without a final error.
If the install fails entirely, try clicking Runtime then Restart runtime and run the cell again.
help me fix Unsloth installation errors in Google Colab
Verify GPU is available
Before you start loading models, you need to confirm that PyTorch can see the GPU and that it has enough VRAM for fine-tuning.
Before you run the next cell, what do you think it will print? Consider what GPU Colab assigned to your notebook.
- Verify your GPU by running this code in a new cell:
# Verify GPU is available
import torch
print(f"GPU: {torch.cuda.get_device_name(0)}")
print(f"VRAM: {torch.cuda.get_device_properties(0).total_mem / 1024**3:.1f} GB")
What does this code do?
- torch.cuda.get_device_name(0) asks PyTorch for the name of the first GPU. On Colab's free tier, this is typically a Tesla T4.
- torch.cuda.get_device_properties(0).total_mem returns the total VRAM in bytes, which you divide to get a human-readable GB value.
You should see output showing Tesla T4 with approximately 15.0 GB of VRAM. This confirms your notebook has the GPU power needed for fine-tuning.
Seeing a CUDA error or wrong GPU?
If you see a CUDA-related error, your runtime may not have a GPU assigned. Go to Runtime then Change runtime type and confirm T4 GPU is selected.
If you see a different GPU name (like L4 or A100), that's fine. Any GPU with 15+ GB VRAM works for this project.
help me fix GPU detection issues in Google Colab
✔️ Awesome, I've got everything!
Great. Double check you see Tesla T4 (or another GPU) and ~15 GB VRAM printed in your output, then move on.
ⓧ I'd like to double check the full code
Here is what your notebook should contain across two cells at this point:
Cell 1:
!pip install unsloth
!pip install datasets trl
Cell 2:
# Verify GPU is available
import torch
print(f"GPU: {torch.cuda.get_device_name(0)}")
print(f"VRAM: {torch.cuda.get_device_properties(0).total_mem / 1024**3:.1f} GB")
Your GPU environment is ready. Next up, you'll load the base model and see firsthand why fine-tuning is necessary.
Load the Base Model and See It Fail
Your GPU environment is ready and Unsloth is installed. Now it's time to load a language model and put it to the test.
Before you invest time fine-tuning, you need to see why fine-tuning is necessary. The base model was trained on general internet text. It knows nothing about Corvit Systems, their specific courses, durations, or programs. Asking it Corvit-specific questions will reveal this gap clearly.
In this step, get ready to:
- Load the Qwen 2.5 1.5B model in 4-bit quantization.
- Ask the base model questions about Corvit Systems courses.
- Save the base model's responses for later comparison.
Load Qwen 2.5 1.5B in 4-bit quantization
4-bit quantization compresses a model's weights so it fits within your T4's 15 GB of VRAM. Unsloth provides pre-quantized versions that skip the slow download-and-convert step.
- Create a new code cell in your Colab notebook and load the base model by running this code:
# Cell 3: Load base model
from unsloth import FastLanguageModel
model, tokenizer = FastLanguageModel.from_pretrained(
model_name="unsloth/Qwen2.5-1.5B-Instruct-bnb-4bit",
max_seq_length=2048,
load_in_4bit=True,
)
What does this code do?
- FastLanguageModel.from_pretrained() downloads and loads a pre-quantized model from Unsloth's model hub.
- unsloth/Qwen2.5-1.5B-Instruct-bnb-4bit is a 1.5 billion parameter chat model that's already compressed to 4-bit precision.
- max_seq_length=2048 sets the maximum number of tokens the model can process in one conversation.
- The function returns both the model (the neural network) and the tokenizer (which converts text into numbers the model understands).
- Run the cell. You should see download progress bars as the model files are fetched, followed by a confirmation that the model loaded successfully. This takes 1-2 minutes.
Seeing a CUDA out of memory error?
Make sure your runtime type is set to T4 GPU. Go to Runtime then Change runtime type and confirm T4 GPU is selected.
If you previously ran other code that consumed GPU memory, go to Runtime then Restart session and re-run your cells from the top.
Help me debug a CUDA out of memory error when loading the model in Colab.
Ask the base model about Corvit courses
Now that the model is loaded, you'll write a helper function to send it questions and print the answers. Then you'll ask it about Corvit Systems' courses.
Before you run the next cell, think about this: has this model ever seen Corvit Systems' website or course catalog during its training? What do you expect it to say when asked about their specific programs?
- Create a new code cell and test the base model with Corvit-specific questions by running this code:
# Cell 4: Test base model (before fine-tuning)
def ask_model(prompt, max_new_tokens=256):
messages = [{"role": "user", "content": prompt}]
text = tokenizer.apply_chat_template(messages, tokenize=False, add_generation_prompt=True)
inputs = tokenizer([text], return_tensors="pt").to("cuda")
with torch.inference_mode():
outputs = model.generate(**inputs, max_new_tokens=max_new_tokens, temperature=0.7, do_sample=True)
response = tokenizer.decode(outputs[0][inputs["input_ids"].shape[1]:], skip_special_tokens=True)
return response
test_questions = [
"What courses does Corvit Systems offer?",
"Tell me about the Cyber Security course at Corvit.",
"Does Corvit offer any free courses?",
"How long is the AI course at Corvit Systems?",
]
base_responses = {}
for q in test_questions:
response = ask_model(q)
base_responses[q] = response
print(f"Q: {q}")
print(f"A: {response}\n")
What does this code do?
- ask_model() formats a user message using the model's chat template, sends it to the GPU, generates a response, and decodes it back to readable text.
- torch.inference_mode() disables gradient tracking to save memory during generation.
- test_questions contains 4 questions that only someone who knows Corvit's actual programs could answer correctly.
- The loop asks each question, saves the response to base_responses for later comparison, and prints both the question and answer.
- Run the cell and read through the model's answers.
The model produces vague, generic, or completely hallucinated answers. It might invent courses Corvit doesn't offer, confuse it with another institution, or give non-specific responses that could apply to any training centre.
Why does the model get it wrong?
Qwen 2.5 1.5B was trained on general internet text. It has no specific knowledge of Corvit Systems' course catalog, durations, campuses, or NAVTTC programs. This is exactly the gap that fine-tuning fills.
The model is not broken. It's doing its best with information it was never given. By the end of this project, these same questions will produce accurate, detailed answers.
Not seeing any output?
Generation can take 10-30 seconds per question on a T4. Wait for the cell to finish running (the spinning icon stops).
If you see a NameError for model or tokenizer, make sure you ran Cell 3 first and it completed without errors.
Help me debug why my model isn't generating responses in Colab.
Record the base model's responses for later comparison
The loop already saved every response into the base_responses dictionary. In Step 5, you'll print these side-by-side with the fine-tuned model's answers to see the dramatic improvement.
Why save the base responses?
Fine-tuning takes time. By the time you test the trained model, you may have forgotten exactly how bad the base model's answers were. Saving them now gives you a concrete before-and-after comparison that proves fine-tuning worked.
The base_responses dictionary maps each question string to the model's original answer. As long as you don't restart the Colab runtime, these stay in memory throughout the project.
✔️ Awesome, I've got everything!
Great. Make sure both Cell 3 and Cell 4 ran successfully and you can see the base model's (incorrect) answers printed in the output.
ⓧ I'd like to double check the full code
Your notebook should contain these cells in order. Scroll up and confirm each is present:
- Cell 1: The install commands (!pip install unsloth and !pip install datasets trl).
- Cell 2: The GPU verification (import torch with the print statements).
- Cell 3: The model loading (FastLanguageModel.from_pretrained with unsloth/Qwen2.5-1.5B-Instruct-bnb-4bit).
- Cell 4: The ask_model function, test_questions list, and the loop that populates base_responses.
You've proven the problem: the base model has no clue about Corvit Systems. Next up, you'll build the training dataset that teaches it everything it needs to know.
Build Your Training Dataset
You saw the base model fail spectacularly on Corvit questions in the previous step. The reason is simple: the model was never shown how a Corvit course advisor should respond.
Fine-tuning fixes this by giving the model concrete examples of the exact behaviour you want. In this step, you will build a training dataset of conversational Q&A pairs that teach the model how to answer Corvit-specific questions.
In this step, get ready to:
- Create conversational training examples about Corvit courses.
- Convert your examples into a Hugging Face Dataset object.
- Apply the chat template to format examples for training.
Create conversational training examples
Each training example is a conversation between a user asking a question and an assistant giving the ideal response. The model learns from these examples during fine-tuning.
- Create a new code cell in your Colab notebook and paste the following training data:
# Cell 5: Build training dataset
training_data = [
{"messages": [{"role": "user", "content": "What is Corvit Systems?"}, {"role": "assistant", "content": "Corvit Systems is one of Pakistan's most trusted and established IT training institutes, operating since 2000. With over two decades of experience, Corvit offers professional IT education through globally aligned and government-recognized training programs. They have campuses in Lahore (head office), Islamabad, Rawalpindi, Peshawar, and Muzaffarabad."}]},
{"messages": [{"role": "user", "content": "What courses does Corvit Systems offer?"}, {"role": "assistant", "content": "Corvit Systems offers a diverse portfolio of IT courses including: Artificial Intelligence (Machine Learning and Deep Learning), Cyber Security and Ethical Hacking, Full Stack Web Development, Cloud Computing (AWS), DevOps, Digital Marketing and SEO, Graphic Designing and Video Editing, and networking certifications like CCNA, CCNP, and CCIE. They also offer vendor certifications from Cisco, Huawei, Juniper, Microsoft, VMware, and Red Hat."}]},
{"messages": [{"role": "user", "content": "Tell me about the AI course at Corvit."}, {"role": "assistant", "content": "The Artificial Intelligence course at Corvit Systems is a 3-month program covering Machine Learning and Deep Learning. It includes specialization in Natural Language Processing (NLP) and Microsoft Azure AI Associate. You will work with tools like scikit-learn, pandas, NumPy, TensorFlow, PyTorch, and Keras. The course is 80% practical and 20% theory, with 4 hours of class per day. It also includes 2 weeks of on-the-job training."}]},
{"messages": [{"role": "user", "content": "What is covered in the Cyber Security course?"}, {"role": "assistant", "content": "The Cyber Security course at Corvit covers the complete spectrum of modern cyber defence. Topics include: Introduction to Cyber Security Fundamentals, Information Gathering and Footprinting, Reconnaissance and Enumeration, Vulnerability Assessment, Linux OS Fundamentals, Social Engineering, System Hacking, Firewalls (Cisco ASA, Palo Alto, Fortinet), Network Security, Web Application Security, Ethical Hacking, Sniffing Techniques, DoS/DDoS attacks, SQL Injections, Wireless Network Hacking, and Cloud Security."}]},
{"messages": [{"role": "user", "content": "Tell me about the Web Development course."}, {"role": "assistant", "content": "Corvit's CIT Web Development course is a 3-month program. It covers front-end development with HTML, CSS, Bootstrap, and JavaScript, then moves to back-end with PHP, MySQL, and the Laravel framework. You will build CRUD applications, a Gallery App, and a final project combining both. The course also covers jQuery, database management with PHPMyAdmin, and Laravel concepts like migrations, relationships, and security."}]},
{"messages": [{"role": "user", "content": "Does Corvit offer free courses?"}, {"role": "assistant", "content": "Yes! Corvit Systems offers free IT training programs under the NAVTTC (National Vocational and Technical Training Commission) initiative. These are fully funded by the Government of Pakistan with no tuition fees for eligible students. Free NAVTTC courses include: Full Stack Web Development, Cyber Security, Artificial Intelligence, Graphic Designing and Video Editing, Digital Marketing and SEO, Cloud Computing (AWS), and DevOps. Each program includes hands-on training, assessments, and an official NAVTTC certificate."}]},
{"messages": [{"role": "user", "content": "What Cloud Computing courses are available?"}, {"role": "assistant", "content": "Corvit offers Cloud Computing training with a strong focus on Amazon Web Services (AWS). The program covers deployment, scalability, security, and cloud infrastructure management. They offer both AWS Associate and AWS Professional level certifications. Microsoft Azure training is also available. The cloud computing courses are available both online and on-campus, and are also offered as a free NAVTTC course."}]},
{"messages": [{"role": "user", "content": "What is the DevOps course about?"}, {"role": "assistant", "content": "The DevOps course at Corvit Systems provides end-to-end training on CI/CD pipelines, automation, containerization, and cloud-native deployment. It is aligned with industry standards and prepares you for modern DevOps engineering roles. DevOps is also available as a free NAVTTC-funded course. The training is available both online and on-campus."}]},
{"messages": [{"role": "user", "content": "Does Corvit offer online classes?"}, {"role": "assistant", "content": "Yes, Corvit Systems offers both online and on-campus training options. Their online programs are customized across diverse skill sets and targeted job descriptions. They cater to busy professionals who prefer self-paced learning. Many courses including CCNA, Cyber Security, Cloud Computing, and AI are available in online format with live instructor-led sessions."}]},
{"messages": [{"role": "user", "content": "Where is Corvit located?"}, {"role": "assistant", "content": "Corvit Systems has multiple campuses across Pakistan. The head office is in Lahore at 11A-D1, Ghalib Road, Gulberg III. Other locations include: Islamabad at 70-W, Al-Malik Center, Jinnah Avenue, Blue Area; Rawalpindi at 2nd Floor Zarwar Center, Murree Road; Peshawar at 1st Floor, Ali Tower, University Road; and Muzaffarabad at Tech Era College, Bank Road. Contact: +92-303-8888555."}]},
]
What does this structure look like?
Each entry has a messages key containing a list of role/content pairs. The user role is the question, and the assistant role is the ideal answer you want the model to learn.
This conversational format is what SFTTrainer expects. The model learns to produce the assistant responses when it sees similar user questions.
These 10 examples demonstrate the structure. You need approximately 50 total examples to give the model enough signal to learn from.
- Continue adding examples to training_data until you have around 50 entries. Cover these categories:
- Networking certifications (CCNA, CCNP, CCIE, Huawei, Juniper).
- NAVTTC eligibility and details.
- Course durations and schedules.
- Degree programs (CIITMS, UET Taxila affiliation).
- Campus-specific details (Peshawar, Islamabad, etc.).
- Job opportunities after courses.
- Contact information and enrolment process.
Quality over quantity
Each assistant answer should be detailed and specific. Vague one-line answers teach the model to be vague. Include course names, durations, tools, and specific facts in every response.
- Run the cell to store your training_data list in memory.
Seeing a syntax error?
The most common issue is a missing comma between dictionary entries. Each entry line must end with }, except for the very last one. Also check that all strings use matching quote types.
help me fix a syntax error in my training_data list
Convert to a Hugging Face Dataset and apply the chat template
The SFTTrainer from TRL expects data in a Hugging Face Dataset format with a text column containing the pre-formatted chat. You need to convert your Python list into a Dataset and then apply the tokenizer's chat template to each example.
- Create a new cell and paste the following code to convert and format your dataset:
# Cell 6: Convert to HuggingFace Dataset
from datasets import Dataset
dataset = Dataset.from_list(training_data)
def format_chat(example):
text = tokenizer.apply_chat_template(example["messages"], tokenize=False, add_generation_prompt=False)
return {"text": text}
dataset = dataset.map(format_chat)
print(dataset[0]["text"][:500])
What does this code do?
- Dataset.from_list(training_data) converts your Python list of dictionaries into a Hugging Face Dataset object backed by Apache Arrow for fast processing.
- format_chat uses the tokenizer's apply_chat_template to wrap each conversation in the special tokens that Qwen expects (system prompts, turn markers, end-of-turn tokens).
- dataset.map(format_chat) applies this formatting to every example, adding a text column the trainer will read.
- The final print shows the first 500 characters of the formatted output so you can verify the structure looks correct.
Before you run this cell, what do you expect the formatted text to look like? Think about how a chat model separates user and assistant turns.
- Run the cell.
You should see output containing special chat template tokens wrapping the user and assistant messages. The exact tokens vary by model, but you will see clear role markers separating the question from the answer.
Seeing a KeyError on messages?
This means your training data dictionaries are missing the messages key. Double-check that each entry follows the {"messages": [{"role": "user", ...}, {"role": "assistant", ...}]} structure exactly.
help me fix a KeyError in my training dataset formatting
✔️ Awesome, I've got everything!
Great. Make sure you have saved both cells (Cell 5 and Cell 6) and that the print output shows recognisable chat structure.
ⓧ I'd like to double check the full code
Scroll up and confirm these blocks are present in order in your notebook:
- Cell 5: training_data list with approximately 50 Q&A entries covering Corvit's courses, locations, NAVTTC programmes, schedules, and general info.
- Cell 6: Imports Dataset from datasets, converts the list with Dataset.from_list(training_data), defines format_chat using apply_chat_template, maps it over the dataset, and prints the first 500 characters.
Your dataset is ready for training. Next up, you will attach LoRA adapters to the model and run the fine-tuning loop.
Configure QLoRA and Fine-Tune
Your dataset is ready with 50 conversational examples that teach the model how a Corvit course advisor should respond. The base model has already proven it knows nothing about Corvit.
Now you need to actually train the model on your data. QLoRA makes this possible on a free T4 GPU by keeping the base model frozen in 4-bit precision and training only small LoRA adapter matrices. You end up training roughly 1% of the total parameters.
In this step, get ready to:
- Attach LoRA adapters to the frozen base model.
- Configure training hyperparameters and run fine-tuning.
- Save the trained adapter locally for reuse.
Attach LoRA adapters to the model
LoRA adapters are small trainable matrices injected into specific layers of the frozen base model. Instead of updating all 1.5 billion parameters, you only train the adapters.
- Create a new code cell in your Colab notebook and attach LoRA adapters by running this code:
# Cell 7: Attach LoRA adapters
model = FastLanguageModel.get_peft_model(
model,
r=16,
target_modules=["q_proj", "k_proj", "v_proj", "o_proj",
"gate_proj", "up_proj", "down_proj"],
lora_alpha=16,
lora_dropout=0,
bias="none",
use_gradient_checkpointing="unsloth",
random_state=42,
)
What do these LoRA parameters mean?
- r=16 is the rank of the adapter matrices. Higher rank means more capacity to learn, but also more memory. 16 is a solid default.
- target_modules lists the attention and feed-forward layers that get adapters. These seven modules cover the full attention mechanism plus the MLP layers.
- use_gradient_checkpointing="unsloth" uses Unsloth's optimised checkpointing that reduces VRAM usage by 30%.
- lora_dropout=0 is optimised for Unsloth's training pipeline.
- Run the cell. You should see output showing the number of trainable parameters versus total parameters.
The output confirms that only a small fraction of total parameters are trainable. The base model stays frozen in 4-bit while your LoRA adapters do all the learning.
Seeing an out-of-memory error?
Make sure your runtime is set to T4 GPU. Go to Runtime > Change runtime type and confirm T4 GPU is selected. If your session ran out of memory from earlier cells, click Runtime > Restart session and re-run all cells from the top.
Why isn't my LoRA adapter attaching to the model?
Configure and launch training
With adapters attached, you configure the Supervised Fine-Tuning trainer. This tells the model how to learn from your dataset: how fast, how many times, and in what batch sizes.
- Create a new code cell and configure training by running this code:
# Cell 8: Train the model
from trl import SFTTrainer, SFTConfig
trainer = SFTTrainer(
model=model,
tokenizer=tokenizer,
train_dataset=dataset,
args=SFTConfig(
per_device_train_batch_size=2,
gradient_accumulation_steps=4,
warmup_steps=5,
num_train_epochs=3,
learning_rate=2e-4,
logging_steps=5,
optim="adamw_8bit",
seed=42,
output_dir="outputs",
dataset_text_field="text",
max_seq_length=2048,
),
)
trainer.train()
What do these training settings mean?
- num_train_epochs=3 means the model sees every training example 3 times. Enough to memorise domain knowledge without severe overfitting.
- learning_rate=2e-4 controls how aggressively the adapters update. This is a standard rate for LoRA fine-tuning.
- optim="adamw_8bit" uses an 8-bit optimizer to save GPU memory while training.
- dataset_text_field="text" tells the trainer to use the pre-formatted text column you created in the previous step.
Before you run this cell, what do you think will happen to the training loss over 3 epochs?
- Run the cell. Training takes roughly 3-5 minutes on a free T4.
You should see a progress bar and training logs printing every 5 steps. The training loss starts around 2.0 and drops below 1.0 by the end. That falling loss means the model is learning your Corvit course data.
Training is very slow or stuck?
If training is taking much longer than 5 minutes, your session may have been assigned a slower GPU. Go to Runtime > Change runtime type and confirm T4 GPU is selected. If the cell produces no output at all, click the stop button and re-run it.
Why is my fine-tuning training taking too long or not showing progress?
Save the trained adapter locally
Training is done. The adapter weights now live in GPU memory, but Colab sessions are temporary. You need to save the adapter to disk so it persists for the rest of this session.
- Create a new code cell and save your trained adapter by running:
# Cell 9: Save the adapter
model.save_pretrained("corvit-advisor-lora")
tokenizer.save_pretrained("corvit-advisor-lora")
print("Adapter saved to corvit-advisor-lora/")
What gets saved here?
Only the LoRA adapter weights are saved, not the full 1.5B model. The adapter is roughly 50MB. To use it again, you load the base model and apply this adapter on top. This is what makes LoRA portable.
- Run the cell. You should see Adapter saved to corvit-advisor-lora/ printed in the output.
Don't see the saved message?
Make sure you ran the training cell (Cell 8) completely before running Cell 9. If you restarted your session mid-way, you need to re-run all cells from the top in order.
Why can't I save my LoRA adapter in Colab?
✔️ Awesome, I've got everything!
Great. Double check you've saved your notebook in Colab (Ctrl+S or Cmd+S).
ⓧ I'd like to double check the full code
Scroll through your Colab notebook and confirm these three cells are present, in order:
- Cell 7: Attach LoRA adapters calling FastLanguageModel.get_peft_model with r=16 and 7 target modules.
- Cell 8: Train the model importing SFTTrainer and SFTConfig, configuring the trainer with 3 epochs and adamw_8bit optimizer, then calling trainer.train().
- Cell 9: Save the adapter calling model.save_pretrained("corvit-advisor-lora") and tokenizer.save_pretrained("corvit-advisor-lora").
Your model is now fine-tuned and saved. Next up, you'll put it to the test and see the dramatic before-and-after difference when you ask it about Corvit's courses.
Test Your Fine-Tuned Chatbot
Training is complete, but you have no proof yet that it actually worked. The model's weights changed during training, but did it learn the right things about Corvit's courses?
This is the moment of truth. You will ask the same questions from Step 2 and compare the fine-tuned model's responses against the base model's failures. A clear before/after comparison is the strongest evidence that supervised fine-tuning worked.
In this step, get ready to:
- Switch the model to fast inference mode and test it on your original questions.
- Print a side-by-side before/after comparison proving the fine-tuning worked.
- Test generalisation by asking questions the model has never seen.
Switch to inference mode and test
During training, the model uses extra memory for gradients and optimizer states. Switching to inference mode disables all of that, making generation faster and freeing up GPU memory.
- Create a new code cell in your Colab notebook and enable fast inference by running this code:
# Cell 10: Test fine-tuned model
FastLanguageModel.for_inference(model)
print("=" * 60)
print("FINE-TUNED MODEL RESPONSES")
print("=" * 60)
for q in test_questions:
response = ask_model(q)
print(f"\nQ: {q}")
print(f"A: {response}")
What does this code do?
FastLanguageModel.for_inference(model) switches the model from training mode to optimised inference mode. This disables gradient tracking and uses Unsloth's speed optimisations for faster text generation.
The loop reuses the same test_questions list and ask_model function from Step 2. The only difference is that the model now has your trained LoRA adapters active.
Before you run this cell, what do you expect the model to say when asked "What courses does Corvit Systems offer?" Will it hallucinate like before, or give accurate details?
- Run the cell and read through the responses.
The model now responds with accurate details about Corvit's AI, Cyber Security, Web Development, Cloud Computing, and DevOps courses. Compare this to the vague or hallucinated responses you saw in Step 2.
Still seeing generic or wrong answers?
Make sure you ran the training cell (Cell 8) to completion before this cell. Check that the training loss dropped below 1.0 in the training log output.
If you restarted your Colab runtime after training, you need to reload the saved adapter from corvit-advisor-lora/ rather than re-running from scratch.
Help me debug why my fine-tuned model is still giving wrong answers about Corvit courses.
Compare base vs fine-tuned side by side
Seeing accurate answers is good, but the real impact lands when you see the base model's failures and the fine-tuned model's answers printed next to each other. This is the comparison that proves fine-tuning's value.
- Create a new code cell and print the side-by-side comparison by running:
# Cell 11: Side-by-side comparison
print("\n" + "=" * 60)
print("BEFORE vs AFTER COMPARISON")
print("=" * 60)
for q in test_questions:
finetuned_response = ask_model(q)
print(f"\nQ: {q}")
print(f"BEFORE: {base_responses[q][:200]}...")
print(f"AFTER: {finetuned_response[:200]}...")
What does this code do?
This loops through all 4 test questions and prints two lines for each. The BEFORE line pulls the saved base model response from the base_responses dict you created in Step 2.
The AFTER line generates a fresh response from the fine-tuned model. Both are truncated to 200 characters so they fit neatly on screen for comparison.
- Run the cell and scroll through the output.
You should see a dramatic difference. The BEFORE responses are vague, generic, or completely wrong. The AFTER responses mention specific Corvit courses, durations, and details from your training data.
Why truncate to 200 characters?
The [:200] slice keeps the comparison readable. Full responses can be 500+ characters. Truncating lets you scan all 4 questions at a glance without scrolling through walls of text.
Test with new questions not in training data
A model that only repeats memorised answers is not useful. The real test is whether it can generalise to questions it has never seen during training.
- Create a new code cell and test generalisation by running:
# Cell 12: Test generalisation with unseen questions
new_questions = [
"Can I study part-time at Corvit?",
"What certifications will I get after the Cyber Security course?",
"Is there a data science course available?",
]
print("\n" + "=" * 60)
print("GENERALISATION TEST - NEW QUESTIONS")
print("=" * 60)
for q in new_questions:
response = ask_model(q)
print(f"\nQ: {q}")
print(f"A: {response}")
What does this code do?
The new_questions list contains 3 questions that were NOT in the 50 training examples. None of these exact phrasings appeared during training.
This tests whether the model learned general knowledge about Corvit (it can reason about the domain) or just memorised exact question-answer pairs (it can only repeat what it saw).
What do you predict will happen? Will the model handle these unseen questions, or will it fall back to generic responses?
- Run the cell and read the responses.
The model should give reasonable Corvit-related answers, drawing on the general patterns it learned. Some answers may be partially correct or slightly less detailed than the memorised ones. That is normal for a 50-example dataset.
Generalisation improves with more data
With only 50 training examples, generalisation is limited. In production fine-tuning, teams use hundreds or thousands of examples to cover more question variations. Even so, 50 examples is enough to prove the concept works.
Getting completely off-topic answers for new questions?
If the unseen questions produce totally unrelated answers, training may have overfit on exact patterns. Check that your training loss did not drop extremely low (below 0.1), which signals memorisation rather than learning.
Help me understand why my fine-tuned model handles training questions well but fails on new ones.
✔️ Awesome, I've got everything!
Great. Double check you have all three test cells (Cells 10, 11, and 12) run and their outputs visible in your notebook.
ⓧ I'd like to double check the full code
Scroll up through your notebook and confirm these three cells are present in order, each with its output:
- Cell 10: Switches to inference mode and prints fine-tuned responses for all 4 test questions.
- Cell 11: Prints the BEFORE vs AFTER comparison for all 4 questions.
- Cell 12: Defines new_questions and prints generalisation test results for 3 unseen questions.
You now have concrete proof that fine-tuning works. The base model knew nothing about Corvit, and your fine-tuned version responds like a knowledgeable course advisor. Next up, take on the Secret Mission to publish your adapter to Hugging Face and stress-test with adversarial questions.
Secret mission
Push to Hugging Face Hub
Your fine-tuned adapter lives only in Colab right now. When the session ends, it vanishes. In this secret mission, you'll publish it to Hugging Face Hub so anyone can load it, then stress-test your chatbot with adversarial edge cases it has never seen.
Clean Up Your Resources
Clean Up Your Resources
This project runs entirely in Google Colab, so there are no ongoing costs. Decide whether to keep your resources available, let them expire naturally, or delete them entirely.
Resources you used:
- Google Colab notebook (free, stored in Google Drive).
- Local LoRA adapter files in Colab (auto-deleted when session ends).
- Hugging Face Hub repository (free, permanent, if you completed the Secret Mission).
Keep everything running
No action needed. Choose this if you want to continue experimenting with your fine-tuned model.
- Your Google Colab notebook stays in Google Drive at no cost.
- Your Hugging Face Hub repository remains publicly accessible for free.
- Download your adapter files before the Colab session expires if you want a local backup. Click the folder icon in Colab's left sidebar, right-click the corvit-advisor-lora folder, and select Download.
Pause - I'll come back to this later
Colab sessions disconnect automatically after idle timeout. No manual pause action is needed.
- Download your adapter files before the session expires. Click the folder icon in Colab's left sidebar, right-click corvit-advisor-lora, and select Download.
- Your notebook remains saved in Google Drive. You can reopen it any time and reconnect to a new GPU runtime.
- Your Hugging Face Hub repository stays available. You can reload the adapter from the Hub in a future session without retraining.
Delete - I don't want to use this again
Remove all project resources permanently.
- Delete the Colab notebook from Google Drive. Open Google Drive, find the notebook file, right-click it, and select Move to trash.
- The local corvit-advisor-lora folder is automatically deleted when your Colab session ends. No action needed.
- Delete your Hugging Face Hub repository (if you completed the Secret Mission). Go to your repository page on huggingface.co, click the Settings tab, scroll to the bottom, and use the delete option. This action is irreversible.
Nice Work!
Nice Work!
You just took a generic LLM that knew nothing about Corvit Systems and turned it into a knowledgeable course advisor. That is the power of supervised fine-tuning.
Here's what you accomplished:
- Fine-tuned a 1.5B parameter model using QLoRA on a free T4 GPU, training only ~1% of the model's parameters with 4-bit quantization.
- Built a custom training dataset of 50 conversational examples and ran supervised fine-tuning to teach domain-specific knowledge to a generic LLM.
- Proved fine-tuning works by comparing base model hallucinations against accurate fine-tuned responses in a side-by-side before/after test.
- Secret Mission: Published your LoRA adapter to Hugging Face Hub and stress-tested the model with adversarial edge-case questions.
Ready to quiz yourself?