From 78e7916fb684487dd0b0c6d317f80ae1cb3925b2 Mon Sep 17 00:00:00 2001 From: Ransford Okpoti Date: Thu, 30 Oct 2025 10:46:58 +0000 Subject: [PATCH] fine-tuning-os-model-qlora --- ...lls-week7-fine-tuning-os-model-qlora.ipynb | 322 ++++++++++++++++++ 1 file changed, 322 insertions(+) create mode 100644 week7/community_contributions/ranskills-week7-fine-tuning-os-model-qlora.ipynb diff --git a/week7/community_contributions/ranskills-week7-fine-tuning-os-model-qlora.ipynb b/week7/community_contributions/ranskills-week7-fine-tuning-os-model-qlora.ipynb new file mode 100644 index 0000000..dc03544 --- /dev/null +++ b/week7/community_contributions/ranskills-week7-fine-tuning-os-model-qlora.ipynb @@ -0,0 +1,322 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": { + "id": "ogushXV4ZGMi" + }, + "source": [ + "# Fine-Tuning an OpenSource Model using QLoRA" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "chQmy4_HXhgr" + }, + "outputs": [], + "source": [ + "%pip install -qU peft trl bitsandbytes datasets wandb" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "zHZJoUeQZJNo" + }, + "outputs": [], + "source": [ + "import torch\n", + "from transformers import (\n", + " AutoModelForCausalLM,\n", + " AutoTokenizer,\n", + " BitsAndBytesConfig,\n", + " TrainingArguments\n", + ")\n", + "from peft import LoraConfig, get_peft_model, prepare_model_for_kbit_training\n", + "from trl import SFTConfig, SFTTrainer\n", + "from datasets import load_dataset" + ] + }, + { + "cell_type": "code", + "source": [ + "MODEL_NAME = \"mistralai/Mistral-7B-v0.1\"\n", + "\n", + "\n", + "bnb_config = BitsAndBytesConfig(\n", + " load_in_4bit=True,\n", + " bnb_4bit_use_double_quant=True,\n", + " bnb_4bit_quant_type=\"nf4\",\n", + " bnb_4bit_compute_dtype=torch.bfloat16\n", + ")" + ], + "metadata": { + "id": "NyDHu1vrZ1gO" + }, + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "code", + "source": [ + "model = AutoModelForCausalLM.from_pretrained(\n", + " MODEL_NAME,\n", + " quantization_config=bnb_config,\n", + " device_map=\"auto\",\n", + " trust_remote_code=True\n", + ")\n", + "\n", + "tokenizer = AutoTokenizer.from_pretrained(MODEL_NAME)\n", + "tokenizer.pad_token = tokenizer.eos_token\n", + "tokenizer.padding_side = \"right\"\n", + "\n", + "\n", + "model = prepare_model_for_kbit_training(model)" + ], + "metadata": { + "id": "v2X2xM-dZ7fN" + }, + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "code", + "source": [ + "lora_config = LoraConfig(\n", + " r=8,\n", + " lora_alpha=16,\n", + " target_modules=[\"q_proj\", \"v_proj\", \"k_proj\", \"o_proj\"],\n", + " lora_dropout=0.05,\n", + " bias=\"none\",\n", + " task_type=\"CAUSAL_LM\"\n", + ")\n", + "\n", + "model = get_peft_model(model, lora_config)\n", + "model.print_trainable_parameters()" + ], + "metadata": { + "id": "akK6pJnLaeIr" + }, + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "code", + "source": [ + "DATA_DIR = '/content/data'\n", + "\n", + "data_files = [\n", + " f'{DATA_DIR}/all_beauty_train.parquet',\n", + "]\n", + "\n", + "dataset = load_dataset('parquet', data_files=data_files, split='train')\n", + "\n", + "train_test = dataset.train_test_split(train_size=100, test_size=20, seed=42)\n", + "train_dataset = train_test[\"train\"]\n", + "\n", + "test_dataset = load_dataset('parquet', data_files=[f'{DATA_DIR}/all_beauty_test.parquet'], split='train')" + ], + "metadata": { + "id": "Nm1O_IjBbNa0" + }, + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "code", + "source": [ + "sft_config = SFTConfig(\n", + " output_dir=\"./price-prediction-qlora\",\n", + " num_train_epochs=1,\n", + " per_device_train_batch_size=4,\n", + " gradient_accumulation_steps=2,\n", + " gradient_checkpointing=True,\n", + " optim=\"paged_adamw_8bit\",\n", + " learning_rate=2e-4,\n", + " lr_scheduler_type=\"cosine\",\n", + " warmup_steps=50,\n", + " logging_steps=10,\n", + " save_strategy=\"no\",\n", + " fp16=False,\n", + " bf16=True,\n", + " max_grad_norm=0.3,\n", + " save_total_limit=2,\n", + " group_by_length=True,\n", + " report_to=\"none\",\n", + " packing=False,\n", + " dataset_text_field=\"text\",\n", + ")\n", + "\n", + "\n", + "trainer = SFTTrainer(\n", + " model=model,\n", + " args=sft_config,\n", + " train_dataset=train_dataset,\n", + ")\n", + "\n" + ], + "metadata": { + "id": "a8Nx8GJTb-Wz" + }, + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "code", + "source": [ + "### Start Training" + ], + "metadata": { + "id": "UmH5E6Xvn8so" + }, + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "code", + "source": [ + "print(\"Starting training...\")\n", + "\n", + "trainer.train()\n", + "\n", + "trainer.model.save_pretrained(\"./price-prediction-final\")\n", + "tokenizer.save_pretrained(\"./price-prediction-final\")\n", + "\n", + "print(\"Training complete! LoRA adapters saved to ./price-prediction-final\")" + ], + "metadata": { + "id": "-nXZ5O_ifFVh" + }, + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "code", + "source": [ + "\n", + "def predict_price_inmemory(prompt, model, tokenizer):\n", + "\n", + " model.eval()\n", + "\n", + " inputs = tokenizer(prompt, return_tensors=\"pt\").to(model.device)\n", + "\n", + " with torch.no_grad():\n", + " with torch.amp.autocast(device_type='cuda', dtype=torch.bfloat16):\n", + " outputs = model.generate(\n", + " **inputs,\n", + " max_new_tokens=10,\n", + " temperature=0.1,\n", + " do_sample=False,\n", + " pad_token_id=tokenizer.eos_token_id\n", + " )\n", + "\n", + " result = tokenizer.decode(outputs[0], skip_special_tokens=True)\n", + "\n", + " if \"Price is $\" in result:\n", + " predicted = result.split(\"Price is $\")[-1].strip()\n", + "\n", + " import re\n", + " match = re.search(r'(\\d+\\.?\\d*)', predicted)\n", + " if match:\n", + " return match.group(1)\n", + " return predicted" + ], + "metadata": { + "id": "LSvPhf-3fYaZ" + }, + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "source": [ + "### Eye-test Validation\n", + "\n", + "Not the best I know, but I wanted to go through the entire process myself and not enough time on my hands." + ], + "metadata": { + "id": "m6L5CET_sXmx" + } + }, + { + "cell_type": "code", + "source": [ + "for item in test_dataset.take(5):\n", + " prompt = item[\"text\"]\n", + " actual_price = item[\"price\"]\n", + "\n", + " predicted_price = float(predict_price_inmemory(prompt, model, tokenizer))\n", + " print(\"\\n\" + \"*\" * 80)\n", + " print(prompt)\n", + "\n", + " print(f\"Prediction: ${predicted_price}. Actual: ${actual_price}. Diff {abs(predicted_price - actual_price):,.2f}\")\n" + ], + "metadata": { + "id": "6pSYhLn_kROQ" + }, + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "source": [ + "## Loading Somewhere in the future\n", + "\n", + "It can even be loaded in a different notebook." + ], + "metadata": { + "id": "OnJAD7YihyAD" + } + }, + { + "cell_type": "code", + "source": [ + "from transformers import AutoModelForCausalLM, AutoTokenizer, BitsAndBytesConfig\n", + "from peft import PeftModel\n", + "import torch\n", + "\n", + "bnb_config = BitsAndBytesConfig(\n", + " load_in_4bit=True,\n", + " bnb_4bit_use_double_quant=True,\n", + " bnb_4bit_quant_type=\"nf4\",\n", + " bnb_4bit_compute_dtype=torch.bfloat16\n", + ")\n", + "\n", + "base_model = AutoModelForCausalLM.from_pretrained(\n", + " MODEL_NAME,\n", + " quantization_config=bnb_config,\n", + " device_map=\"auto\",\n", + " trust_remote_code=True\n", + ")\n", + "\n", + "\n", + "model = PeftModel.from_pretrained(base_model, \"./price-prediction-final\")\n", + "tokenizer = AutoTokenizer.from_pretrained(\"./price-prediction-final\")" + ], + "metadata": { + "id": "5RCClHQHijes" + }, + "execution_count": null, + "outputs": [] + } + ], + "metadata": { + "accelerator": "GPU", + "colab": { + "gpuType": "T4", + "provenance": [] + }, + "kernelspec": { + "display_name": "Python 3", + "name": "python3" + }, + "language_info": { + "name": "python" + } + }, + "nbformat": 4, + "nbformat_minor": 0 +} \ No newline at end of file