{ "cells": [ { "cell_type": "markdown", "metadata": { "id": "KWfW4Mlu_0bv" }, "source": [ "# How to Fine-Tune a Decision Model with TRL (SFT + LoRA)\n", "\n", "\n", "[](https://colab.research.google.com/github/Liquid4All/cookbook/blob/main/finetuning/notebooks/sft_for_decision_models_with_trl.ipynb)\n", "\n", "This tutorial shows you how to fine-tune a decision model, a model that makes decisions instead of generating text, on your own labels.\n", "\n", "We fine-tune our [`d1-3B`](https://huggingface.co/LiquidAI/d1-3B) decision model with Supervised Fine-tuning (SFT) and LoRA using the TRL library, with intent classification for customer support routing on Banking77 as the example.\n", "\n", "Follow along if it's your first time using trl, or take single code snippets for your own workflow\n", "\n", "## ๐ฏ What You'll Find:\n", "- **LoRA + SFT** - Fine-tune the d1-3B decision model on your own decisions using LoRA (from PEFT)\n", "- **Evaluation** - Compare the decision model's accuracy before and after fine-tuning\n", "\n", "## ๐ Prerequisites:\n", "- **GPU Runtime**: Select a GPU in `Runtime` โ `Change runtime type`. A free T4 GPU is enough (about 35 minutes of training)\n", "- **Hugging Face Account**: Only needed to push your model to the Hub" ] }, { "cell_type": "markdown", "metadata": { "id": "moZoi4B2_0by" }, "source": [ "# ๐ฆ Installation & Setup\n", "\n", "First, let's install all the required packages:" ] }, { "cell_type": "code", "execution_count": 1, "metadata": { "id": "TzGr4YRj_0by" }, "outputs": [], "source": [ "!pip install -qqq \"transformers==5.19.0\" \"trl==1.15.0\" \"peft==0.21.2\" \"torchao>0.16.0\" datasets --progress-bar off" ] }, { "cell_type": "markdown", "metadata": { "id": "NmWNMRxA_0by" }, "source": [ "Let's now verify the packages are installed correctly" ] }, { "cell_type": "code", "execution_count": 2, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "tjmdRT-Q_0by", "outputId": "83905727-7333-45cd-c6da-41716352ec29" }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "๐ฆ PyTorch version: 2.11.0+cu130\n", "๐ค Transformers version: 5.19.0\n", "๐ TRL version: 1.15.0\n", "๐๏ธ PEFT version: 0.21.2\n", "๐ข Precision: torch.bfloat16\n" ] } ], "source": [ "import torch\n", "import transformers\n", "import trl\n", "import peft\n", "import os\n", "os.environ[\"WANDB_DISABLED\"] = \"true\"\n", "\n", "print(f\"๐ฆ PyTorch version: {torch.__version__}\")\n", "print(f\"๐ค Transformers version: {transformers.__version__}\")\n", "print(f\"๐ TRL version: {trl.__version__}\")\n", "print(f\"๐๏ธ PEFT version: {peft.__version__}\")\n", "\n", "# bfloat16 on GPUs that support it natively (e.g. A100), float16 otherwise (e.g. T4)\n", "dtype = torch.bfloat16 if torch.cuda.is_bf16_supported(including_emulation=False) else torch.float16\n", "print(f\"๐ข Precision: {dtype}\")" ] }, { "cell_type": "markdown", "metadata": { "id": "yW05aaci_0bz" }, "source": [ "# Loading the model from Transformers ๐ค\n", "\n", "d1-3B ships its own code for the System One API (`system_one` and `system_one_batch`), so we load it with `trust_remote_code=True`.\n", "\n", "We also download the model repository locally. It contains `prompt.py`, the module d1-3B uses to turn a state and a question into a prompt. We will reuse it to build our training data, so that the model sees the exact same prompt during training and inference." ] }, { "cell_type": "code", "execution_count": 3, "metadata": { "colab": { "base_uri": "https://localhost:8080/", "height": 488, "referenced_widgets": [ "08f34c371ef84e0596c6a943f2ca74b7", "f15551b3ef9f4e4eaabae203fd0a655d", "6dea5cec77d9471d8f2d166406b10091", "30f4273b147b48a6ad3a6775b8bd8fbe", "bff8e7324a4a47679c2e723d192c7bfd", "4385c543fa5247e490afe36ee7065352", "5b8b8eb517f4437b9d1a1160fa2a4976", "63a0ee20a3424504b205b7c0ad6783e8", "af5d481810d64c2787753c32b2988678", "d4113b0eb8e647929ad15eb669d4d029", "4ab1bab2f37d458db480d737afac44b9", "2e8f2267344c4ff0a9927cc6b368ba3f", "d8d98136039a4138b9dbff3a2c46a4cc", "daed99ccf1d046e3b65b1130e939f877", "e3f8990f28b5490487449f726244a04c", "d0350d9b7d37448aa23e5130a53d12ff", "b4d10318571d4ae8994a0d42fbe53ae8", "3882477071e84914be04f63b54dd4263", "b01fd91451cc4e669557ee421abcf1be", "f0da9067065942e2a7274937a311964c", "7ca485b91e70478cb7e00bc9762cb387", "406a8e07ab4742a3a9298878d1dd5c25", "1f545f27530f407da514ac1a4d6d03e6", "d1a0bfacf3034e8ea80daacdfde9020b", "f625b14f330b4f67b9a2dcecb6738fbd", "23229cd9ff2b46e0a8ce06129696797b", "e8aac19cdeed47afbe98bc87f976042c", "0b781ea88a64436ab520f37f5f752f25", "55b1981f1b0a4847aeae303ab6a7aa86", "5e24e449a4204d3bb342aebeca3e311e", "c5f598a3da284a548d9b7ac1bb5a8243", "65e995ab3d77490e9a453a0477ef0be9", "6f828335145745d3815739836788de6a", "ee4e3bec60214d6fb32c99d0ba93091a", "a20b20b765164dc4b3bdf3b852c6588d", "8f9d32ecb3aa4383a995236ac2c9f62a", "3be5b3982a0049d3a1f99c1e8a026852", "e878e42022a242f298e08dfb438b3458", "4fff2e0480914628ad3e5c489042d94d", "cbd8cd8dafe04478b0476a160e3d65fe", "a5bc899d8d7b4d3c8365d3d748e7c0a3", "38a0737792f147438f3fc55f911aa31f", "dc661738f08d4b1f9188035bfaaefc5c", "cb54a415436745afb3f163850e0c78c0" ] }, "id": "Bnwq3kAI_0bz", "outputId": "e4c34804-4e09-4532-e4e8-5b4143139326" }, "outputs": [ { "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "08f34c371ef84e0596c6a943f2ca74b7", "version_major": 2, "version_minor": 0 }, "text/plain": [ "Downloading bytes: | 0.00B " ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "2e8f2267344c4ff0a9927cc6b368ba3f", "version_major": 2, "version_minor": 0 }, "text/plain": [ "Reconstructing (incomplete total...): | | 0.00B / 0.00B " ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "1f545f27530f407da514ac1a4d6d03e6", "version_major": 2, "version_minor": 0 }, "text/plain": [ "Fetching 15 files: 0%| | 0/15 [00:00, ?it/s]" ] }, "metadata": {}, "output_type": "display_data" }, { "name": "stdout", "output_type": "stream", "text": [ "๐ Loading tokenizer...\n", "๐ง Loading model...\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ "[transformers] A new version of the following files was downloaded from https://huggingface.co/LiquidAI/d1-3B:\n", "- prompt.py\n", ". Make sure to double-check they do not contain any added malicious code. To avoid downloading new versions of the code file, you can pin a revision.\n", "[transformers] A new version of the following files was downloaded from https://huggingface.co/LiquidAI/d1-3B:\n", "- hybrid.py\n", ". Make sure to double-check they do not contain any added malicious code. To avoid downloading new versions of the code file, you can pin a revision.\n", "[transformers] A new version of the following files was downloaded from https://huggingface.co/LiquidAI/d1-3B:\n", "- api.py\n", "- lfm2_vl.py\n", ". Make sure to double-check they do not contain any added malicious code. To avoid downloading new versions of the code file, you can pin a revision.\n", "[transformers] A new version of the following files was downloaded from https://huggingface.co/LiquidAI/d1-3B:\n", "- runner.py\n", ". Make sure to double-check they do not contain any added malicious code. To avoid downloading new versions of the code file, you can pin a revision.\n" ] }, { "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "ee4e3bec60214d6fb32c99d0ba93091a", "version_major": 2, "version_minor": 0 }, "text/plain": [ "Loading weights: 0%| | 0/707 [00:00, ?it/s]" ] }, "metadata": {}, "output_type": "display_data" }, { "name": "stdout", "output_type": "stream", "text": [ "โ Model loaded successfully!\n", "๐ข Parameters: 3,123,483,888\n", "๐ Vocab size: 125017\n", "๐พ Model size: ~6.2 GB (torch.bfloat16)\n" ] } ], "source": [ "import sys\n", "from huggingface_hub import snapshot_download\n", "from transformers import AutoModel, AutoTokenizer\n", "\n", "model_id = \"LiquidAI/d1-3B\"\n", "\n", "# Download the checkpoint and d1's own code, and make its prompt builder importable\n", "local_dir = snapshot_download(model_id)\n", "sys.path.insert(0, local_dir)\n", "\n", "print(\"๐ Loading tokenizer...\")\n", "tokenizer = AutoTokenizer.from_pretrained(model_id)\n", "\n", "print(\"๐ง Loading model...\")\n", "model = AutoModel.from_pretrained(\n", " model_id,\n", " trust_remote_code=True,\n", " dtype=dtype,\n", " ).to(\"cuda\")\n", "\n", "print(\"โ Model loaded successfully!\")\n", "print(f\"๐ข Parameters: {model.num_parameters():,}\")\n", "print(f\"๐ Vocab size: {len(tokenizer)}\")\n", "print(f\"๐พ Model size: ~{model.num_parameters() * 2 / 1e9:.1f} GB ({dtype})\")" ] }, { "cell_type": "markdown", "metadata": { "id": "7SgLlJD7_0bz" }, "source": [ "# ๐ก What is a decision model?\n", "\n", "A decision model is a model that makes decisions instead of generating text: it answers yes/no, multiple-choice, and scoring questions with a probability for each answer, in a single forward pass. Typical uses are classification, routing and triage, content moderation, and LLM-as-a-judge scoring.\n", "\n", "d1-3B is a 3B parameter decision model built on [LFM2.5-VL-3B](https://huggingface.co/LiquidAI/LFM2.5-VL-3B). It takes a state and a set of questions, and returns typed answers in one forward pass, without generating any output tokens. Each question uses one of the following three primitives:\n", "\n", "- `noul`: Yes or no, as P(yes)\n", "- `choice`: One of named options, with a probability for each\n", "- `score`: A level on an ordered scale of 2 to 10 levels\n", "\n", "Before we fine-tune, let's see how d1-3B makes a decision. We ask a `choice` question about a customer message:" ] }, { "cell_type": "code", "execution_count": 4, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "hDCEcck9_0bz", "outputId": "4079883e-e467-4114-bf33-d1a8f3ace0b8" }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "{'answers': {'team': {'type': 'choice', 'choice': 'billing', 'confidence': 0.982860184342666, 'probabilities': {'billing': 0.982860184342666, 'technical': 0.01091859038543887, 'fraud': 0.0062212252718952575}}}, 'usage': {'input_tokens': 61, 'output_tokens': 0}}\n" ] } ], "source": [ "state = \"I was charged twice this month, please refund one of them.\"\n", "\n", "questions = {\n", " \"team\": {\n", " \"type\": \"choice\",\n", " \"instructions\": \"Which team should handle this?\",\n", " \"criteria\": {\n", " \"billing\": \"Charges, refunds, invoices\",\n", " \"technical\": \"App or site faults\",\n", " \"fraud\": \"Suspected unauthorised use\",\n", " },\n", " },\n", "}\n", "\n", "response = model.system_one(state=state, questions=questions)\n", "\n", "print(response)" ] }, { "cell_type": "markdown", "metadata": { "id": "sk8Q2JWu_0bz" }, "source": [ "Under the hood, d1-3B renders the state and the question into a single prompt. Each option gets a short code (`A`, `B`, `C`, ...), and the prompt ends right after the assistant header:" ] }, { "cell_type": "code", "execution_count": 5, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "Vkp5P3-d_0bz", "outputId": "9f456e8e-9b4b-4293-fdca-5d757f2c71b1" }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "<|startoftext|><|im_start|>user\n", "I was charged twice this month, please refund one of them.\n", "\n", "\n", "QUESTION:\n", "Which team should handle this?\n", "\n", "Options:\n", "A Charges, refunds, invoices\n", "B App or site faults\n", "C Suspected unauthorised use\n", "\n", "Reply with the option code only.<|im_end|>\n", "<|im_start|>assistant\n", "\n" ] } ], "source": [ "from prompt import aliases, as_question, render\n", "\n", "rendered_prompt = render(\n", " tokenizer=tokenizer,\n", " state=state,\n", " q=as_question(questions[\"team\"]),\n", " bos=tokenizer.bos_token,\n", ")\n", "\n", "print(rendered_prompt)" ] }, { "cell_type": "markdown", "metadata": { "id": "TUM-SVJE_0b0" }, "source": [ "The model then runs one forward pass and reads the next-token probabilities at the end of the prompt, the **answer slot**. It only looks at the tokens of the option codes and normalizes them into the probabilities you saw above.\n", "\n", "This means that fine-tuning a decision model is regular SFT where **the completion is a single token: the code of the correct option**, followed by the end-of-turn token `<|im_end|>`." ] }, { "cell_type": "markdown", "metadata": { "id": "wye2m15NRrRL" }, "source": [ "# ๐ฏ Supervised Fine-Tuning (SFT + LoRA)\n", "\n", "SFT teaches the model to make your decisions by training on input-output pairs. For a decision model, the input is the rendered prompt and the output is the code of the correct option.\n", "\n", "We use LoRA (Low-Rank Adaptation) to finetune the model by only training a small number of additional parameters." ] }, { "cell_type": "markdown", "metadata": { "id": "-RlDQ8N0_0b0" }, "source": [ "## Load an SFT Dataset\n", "\n", "We will use [Banking77](https://huggingface.co/datasets/mteb/banking77), a dataset of ~13k online banking customer messages, each labeled with one of 77 fine-grained intents. To keep this notebook fast on a free T4 GPU, we train on 3,000 messages and evaluate on 100. Feel free to increase these numbers with `select(range(...))`." ] }, { "cell_type": "code", "execution_count": 6, "metadata": { "colab": { "base_uri": "https://localhost:8080/", "height": 363, "referenced_widgets": [ "790ff24057d445a39f3e3106238ea409", "5ad214ab224545b3b55e17df681572ec", "e6e6f863669c440d96d5c04f4bb80abd", "35657a7a052047b0a782fdda168a04a8", "277f4f561070488f8125c850a8a75457", "5ed1585a65954ef1b3d5af89df315e9f", "f635fb0c36904d7eaafd360b969a14df", "2cf1e5485a8e4d87b5d7d4b39bf6b2ff", "3963c28eae354ec69145c5e0cc3b31ac", "dec33beb976a4eaabe10040d34eef41b", "6ca05b0dbdec41cb8518d8ccc47be495", "c4b2269eb42a40e69e9cbaa82c5ce095", "d56a0f02cd2c4db3998f1fd26c960a7b", "105c5b4107c746c19fe63f29df50cb83", "376549c7c7f347e0a606bea4c3ce8a5a", "5b805d0c26f348ee87d52d6675c2c7be", "4fe4af61245842aa9d901273c9b3f8d5", "4ee1210385434c66bc1143637e053271", "877f4482b13b40fd8d6333ee242960c6", "b0ed28b8adca4993b6254f83d8a7993b", "50fa7fce1c034c8da4d256f5e5f59b26", "c72d4630423044a29f6a93e720a9d87e", "dbcb439a6b644f4ebcd8aee7119615bd", "41b84513a58446938ec73bf270e3faee", "3ec5f9aa07a844e99bd618d96a24e04c", "5a695cca9567465fac5af384b43a54be", "4fd81d3569f94225b49a5250b76e2795", "05727540aaf2498a9b7fefca99993b42", "b43e4a9848c040e0a5f85278e773bcaa", "1b470a6731224348a54e189ad064308b", "d5c1fa7d135a496f90a12cb442542fcf", "705f31a1bcd84aabbafa407b9c11129e", "994e508818c74593961121ebe425ea07", "2195d4e4f7214f22ba102ca9efbad670", "e4db324893ae4286ae60fc236520f62e", "673da49b7e8447e7941d0755eebc6825", "73f15cf59ded4986a93195123211a204", "264c5c191daa4ceaadcf42f83b5e5437", "7907eac57791444da020a7905244000d", "cc2fdbad769940b9ab108925e9faa6f2", "fbb97612f5264d918b9f79b96a65a587", "9759473ee5c041c5bc7e80187eff8b28", "54a6b5a9a344479abd1fa5301ae1c064", "3d9c0fe445b04ecda29691f8e76876af", "acafaf7e0f3f4edc93ee235f12361c75", "d5449d81eb584994865b68dc64896eb9", "4ebe896db25943fdad06f291b914fb71", "88b5d93291ff4db1a760bfef313c7f59", "e1d07b714c3d4bb986f4034681db4c1c", "c72ba1c3b46d40339e6362410445a173", "261391783ba348e4a322199ea7df6992", "5ab2f2637bdd490aa6e2bada167f1f38", "4893a3bf15974f4781ffb1225efa85e4", "ce0c19a5dae5411cb70d8207d274e7ce", "0404818f06d346d18bc765447141e65c", "9ca3ee46bf9f4067bd5d69b801b7b16e", "33f0bfe1b37943e0adf44005bd56c6ef", "6b7bb36230334726b2833b1e63c62d9a", "1679ec968b524a93ae7fb52090a06e1a", "b91dd43248ec45b89746f3643bf3f0ce", "f3b0167a0f5a4889ad30079bceb6ad06", "a3b66abb98e34ebfa336495323d19e7d", "657c04054f7a4126a675bb43f42dbbdd", "57b48f9f7b7a47bab005f198beaee425", "9e0fbc3e766049acbf48a6e510f7680f", "000cdd6f14054139b90403d780bb8794", "065022d26ad649d8aea463a5a6a26d83", "536aaf7a3bda4f81a60f288c701ba4d2", "861df63510794d23be7ecb28e095a839", "77ed46862db2452dba92805d1d9cda10", "279e497667c94db093938079edfd24df", "553afbd6d8f04ed99b4069dc530e1dfd", "03847fa7a7ef4fe29c42caa96dc0c942", "5a19321a9c954768962899edc5e8ea21", "ec586135341a4bb9b7a121cd5f2f91a7", "dc0b2bcdbde3430fa03684b6a3b76148", "eb6d5980b9eb415d91719f691770678c" ] }, "id": "Y2aUXHNm_0b0", "outputId": "27a7e8b2-c246-4f1e-8e2d-c634c23b3f33" }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "๐ฅ Loading dataset...\n" ] }, { "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "790ff24057d445a39f3e3106238ea409", "version_major": 2, "version_minor": 0 }, "text/plain": [ "README.md: 0%| | 0.00/16.6k [00:00, ?B/s]" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "c4b2269eb42a40e69e9cbaa82c5ce095", "version_major": 2, "version_minor": 0 }, "text/plain": [ "data/train-00000-of-00001.parquet: reconstructing file: 0%| | 0.00B / 294kB " ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "dbcb439a6b644f4ebcd8aee7119615bd", "version_major": 2, "version_minor": 0 }, "text/plain": [ "data/train-00000-of-00001.parquet: downloading bytes: | 0.00B " ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "2195d4e4f7214f22ba102ca9efbad670", "version_major": 2, "version_minor": 0 }, "text/plain": [ "data/test-00000-of-00001.parquet: reconstructing file: 0%| | 0.00B / 91.4kB " ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "acafaf7e0f3f4edc93ee235f12361c75", "version_major": 2, "version_minor": 0 }, "text/plain": [ "data/test-00000-of-00001.parquet: downloading bytes: | 0.00B " ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "9ca3ee46bf9f4067bd5d69b801b7b16e", "version_major": 2, "version_minor": 0 }, "text/plain": [ "Generating train split: 0%| | 0/9993 [00:00, ? examples/s]" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "065022d26ad649d8aea463a5a6a26d83", "version_major": 2, "version_minor": 0 }, "text/plain": [ "Generating test split: 0%| | 0/3076 [00:00, ? examples/s]" ] }, "metadata": {}, "output_type": "display_data" }, { "name": "stdout", "output_type": "stream", "text": [ "โ Dataset loaded:\n", " ๐ Train samples: 3000\n", " ๐งช Test samples: 100\n", "\n", "๐ Single Sample: {'text': 'How long is a top up supposed to be pending, because an hour is too long! Can you fix this to go through?', 'label': 47, 'label_text': 'pending_top_up'}\n" ] } ], "source": [ "from datasets import load_dataset\n", "\n", "print(\"๐ฅ Loading dataset...\")\n", "dataset = load_dataset(\"mteb/banking77\")\n", "train_dataset = dataset[\"train\"].shuffle(seed=42).select(range(3000))\n", "test_dataset = dataset[\"test\"].shuffle(seed=42).select(range(100))\n", "\n", "print(\"โ Dataset loaded:\")\n", "print(f\" ๐ Train samples: {len(train_dataset)}\")\n", "print(f\" ๐งช Test samples: {len(test_dataset)}\")\n", "print(f\"\\n๐ Single Sample: {train_dataset[0]}\")" ] }, { "cell_type": "markdown", "metadata": { "id": "ab48de7f5d20" }, "source": [ "## Data processing\n", "\n", "Banking77 contains reworded versions of the same request, such as \"can you tell me why my transfer failed?\" and \"Can you please tell me why my transfer failed?\". If one lands in training and the other in evaluation, the evaluation rewards memorization and overstates what fine-tuning gained.\n", "\n", "So we split by **case**: messages that ask the same thing stay on the same side. If your own data has a case, ticket, or conversation ID, group by that. Banking77 has none, so we approximate one: two messages belong to the same case when they share at least 80% of their words." ] }, { "cell_type": "code", "execution_count": 7, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "2a9d515d2ecf", "outputId": "8db8c62a-9be4-4c31-8630-4375edf8f5cd" }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "๐งน Dropped 6 training messages that reword a test message\n", "๐ 77 training messages reword another training message\n", "โ No case on both sides: 2694 train and 300 eval messages\n" ] } ], "source": [ "import random\n", "import re\n", "from collections import Counter\n", "\n", "def words(text):\n", " return set(re.findall(r\"[a-z0-9']+\", text.lower()))\n", "\n", "def same_case(a, b, threshold=0.8):\n", " \"\"\"Whether two messages' word sets overlap enough to count as rewordings of one request.\"\"\"\n", " union = a | b\n", " return bool(union) and len(a & b) / len(union) >= threshold\n", "\n", "def case_ids(texts):\n", " \"\"\"Give messages that reword each other the same case ID, transitively.\"\"\"\n", " word_sets = [words(text) for text in texts]\n", " parent = list(range(len(texts)))\n", "\n", " def find(i):\n", " while parent[i] != i:\n", " i = parent[i]\n", " return i\n", "\n", " for i in range(len(texts)):\n", " for j in range(i + 1, len(texts)):\n", " if same_case(word_sets[i], word_sets[j]):\n", " parent[find(j)] = find(i)\n", " return [find(i) for i in range(len(texts))]\n", "\n", "# 1. Drop training messages that reword a test message\n", "test_words = [words(text) for text in test_dataset[\"text\"]]\n", "leaked = {i for i, text in enumerate(train_dataset[\"text\"]) if any(same_case(words(text), w) for w in test_words)}\n", "train_dataset = train_dataset.select([i for i in range(len(train_dataset)) if i not in leaked])\n", "\n", "# 2. Hold out 10% of the training messages to track the loss, taking whole cases only\n", "cases = case_ids(train_dataset[\"text\"])\n", "case_sizes = Counter(cases)\n", "shuffled_cases = sorted(case_sizes)\n", "random.Random(42).shuffle(shuffled_cases)\n", "eval_cases, n_eval = set(), 0\n", "for case in shuffled_cases:\n", " if n_eval >= 0.1 * len(train_dataset):\n", " break\n", " eval_cases.add(case)\n", " n_eval += case_sizes[case]\n", "\n", "sft_train = train_dataset.select([i for i, case in enumerate(cases) if case not in eval_cases])\n", "sft_eval = train_dataset.select([i for i, case in enumerate(cases) if case in eval_cases])\n", "\n", "print(f\"๐งน Dropped {len(leaked)} training messages that reword a test message\")\n", "print(f\"๐ {len(cases) - len(case_sizes)} training messages reword another training message\")\n", "print(f\"โ No case on both sides: {len(sft_train)} train and {len(sft_eval)} eval messages\")" ] }, { "cell_type": "markdown", "metadata": { "id": "yXo0fh3e_0b0" }, "source": [ "## Define the decision\n", "\n", "We turn the classification task into a single `choice` question. Each intent becomes one option, described by its label name.\n", "\n", "**Important**: The option codes are assigned by position: the first option gets the first code, the second option the second, and so on. If we always trained with the options in the same order, the model could learn which code means which intent without reading the option list. Adding or reordering an intent later would then shift the codes and break the fine-tuned model. We avoid that below by giving every training example its own random subset of the intents, in a random order." ] }, { "cell_type": "code", "execution_count": 8, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "N7-7bCmQ_0b0", "outputId": "d6285358-89a0-48d0-d566-fa543ca3fa10" }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "๐ท๏ธ Number of options: 77\n", "๐ค First option codes: {'Refund_not_showing_up': '00', 'activate_my_card': '01', 'age_limit': '02', 'apple_pay_or_google_pay': '03', 'atm_support': '04'}\n" ] } ], "source": [ "labels = sorted(set(dataset[\"train\"][\"label_text\"]))\n", "\n", "intent_question = {\n", " \"type\": \"choice\",\n", " \"instructions\": \"Which intent does this banking customer message express?\",\n", " \"criteria\": {label: label.replace(\"_\", \" \") for label in labels},\n", "}\n", "\n", "# The code d1-3B assigns to each option in this order, e.g. \"card_arrival\" -> \"12\"\n", "codes = {label: code for label, (code, _) in zip(labels, aliases(tokenizer, labels))}\n", "\n", "print(f\"๐ท๏ธ Number of options: {len(labels)}\")\n", "print(f\"๐ค First option codes: {dict(list(codes.items())[:5])}\")" ] }, { "cell_type": "markdown", "metadata": { "id": "CYeMBFH9_0b0" }, "source": [ "With up to 26 options, d1-3B uses the letters `A` to `Z` as codes. With more options, as here, it switches to the two-digit codes `00` to `76`, each of which is a single token. Going from 26 to 27 options therefore changes every code at once, which is another reason to train on shuffled option lists. Our subsets keep at least 27 options, so the codes stay two-digit, as they are when we ask with all 77 intents." ] }, { "cell_type": "markdown", "metadata": { "id": "-He7VzSs_0b0" }, "source": [ "We now transform the dataset to a format compatible with `trl`. We use TRL's **prompt-completion** format:\n", "- `prompt`: the full prompt rendered by d1-3B's own prompt builder, with its own option list: the correct intent plus a random number of the other intents (27 to 77 options in total), in a random order\n", "- `completion`: the code the correct intent gets in that list, followed by `<|im_end|>`\n", "\n", "Because the list changes from example to example, a code says nothing about which intent it stands for: the model has to read the option list to find the right one, whichever intents it contains.\n", "\n", "With a prompt-completion dataset, `SFTTrainer` computes the loss only on the completion, so the model learns to put its probability mass on the right option code." ] }, { "cell_type": "code", "execution_count": 9, "metadata": { "colab": { "base_uri": "https://localhost:8080/", "height": 1000, "referenced_widgets": [ "fcb2e8c77ede4224a965fa5649995660", "c9b18bcee5ec46c48a9b05606550b845", "0cc4d236f51546c19331049719202221", "08cbe200db9b4d7c9cb7ceb74ead6a0e", "cefff3b520a346b7b6c3f25098fea1f2", "dfb21247fcb94b0f80d0da7f54a20bbb", "47420bdaa4b64154a6ff5c85bd1c730e", "35c3f74ffe9c49edbde61ea63f0e0703", "90b3808ea58c4d2f988c0e5eecd50407", "87df7756ca9e4be4ac93ce67eefc6111", "ab7c96a1b1dd4b8789e430397ed8c199", "06ccbc7d9c364d8786882ce597ce02e1", "3feeaad4b9284bb280735c5685e5d36b", "12b22c015b9e4336938ae55080575c5c", "b07e164fc19840608965fda01be666ad", "6ef008d01e5b4164a8a26673a8376b8b", "0bd1f63ceed8404d8dc35e7a06bd827b", "b6f5b31600bd42a597f8d60c2b14870e", "f59e08db3272417593c6fcdf8d488aa7", "c8cc464bf501474197701b475fcc1eca", "1ffb52a141fb4336bde86defe8ac768f", "eb870c2dada44055a5b971513421f7aa" ] }, "id": "Oh7aE-kN_0b0", "outputId": "ff75a384-048c-4278-a817-ecd7f4cf2df2" }, "outputs": [ { "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "fcb2e8c77ede4224a965fa5649995660", "version_major": 2, "version_minor": 0 }, "text/plain": [ "Map: 0%| | 0/2694 [00:00, ? examples/s]" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "06ccbc7d9c364d8786882ce597ce02e1", "version_major": 2, "version_minor": 0 }, "text/plain": [ "Map: 0%| | 0/300 [00:00, ? examples/s]" ] }, "metadata": {}, "output_type": "display_data" }, { "name": "stdout", "output_type": "stream", "text": [ "โ SFT Dataset formatted:\n", " ๐ Train samples: 2694\n", " ๐งช Eval samples: 300\n", "\n", "๐ Prompt:\n", "<|startoftext|><|im_start|>user\n", "Whats the process of tranfering money into an account?\n", "\n", "\n", "QUESTION:\n", "Which intent does this banking customer message express?\n", "\n", "Options:\n", "00 reverted card payment?\n", "01 card acceptance\n", "02 cash withdrawal charge\n", "03 card swallowed\n", "04 declined cash withdrawal\n", "05 get physical card\n", "06 wrong exchange rate for cash withdrawal\n", "07 request refund\n", "08 wrong amount of cash received\n", "09 automatic top up\n", "10 transaction charged twice\n", "11 apple pay or google pay\n", "12 top up by bank transfer charge\n", "13 edit personal details\n", "14 card payment not recognised\n", "15 card linking\n", "16 verify my identity\n", "17 supported cards and currencies\n", "18 extra charge on statement\n", "19 compromised card\n", "20 age limit\n", "21 terminate account\n", "22 top up by card charge\n", "23 card delivery estimate\n", "24 visa or mastercard\n", "25 direct debit payment not recognised\n", "26 atm support\n", "27 order physical card\n", "28 cash withdrawal not recognised\n", "29 exchange charge\n", "30 balance not updated after bank transfer\n", "31 why verify identity\n", "32 lost or stolen phone\n", "33 disposable card limits\n", "34 get disposable virtual card\n", "35 declined card payment\n", "36 transfer into account\n", "37 verify source of funds\n", "38 unable to verify identity\n", "39 transfer timing\n", "40 cancel transfer\n", "41 card payment wrong exchange rate\n", "42 country support\n", "43 getting spare card\n", "44 card arrival\n", "45 Refund not showing up\n", "46 passcode forgotten\n", "47 pending cash withdrawal\n", "48 top up by cash or cheque\n", "49 transfer not received by recipient\n", "50 card not working\n", "51 pin blocked\n", "\n", "Reply with the option code only.<|im_end|>\n", "<|im_start|>assistant\n", "\n", "\n", "๐ฏ Completion: '36<|im_end|>'\n" ] } ], "source": [ "def to_prompt_completion(example, idx, split):\n", " # Every example gets its own option list: the correct intent plus 26 to 76 others, in a random order\n", " rng = random.Random(f\"{split}-{idx}\")\n", " others = [label for label in labels if label != example[\"label_text\"]]\n", " options = rng.sample(others, rng.randint(26, len(others))) + [example[\"label_text\"]]\n", " rng.shuffle(options)\n", " question = as_question({**intent_question, \"criteria\": {label: intent_question[\"criteria\"][label] for label in options}})\n", " option_codes = {label: code for label, (code, _) in zip(options, aliases(tokenizer, options))}\n", " return {\n", " \"prompt\": render(tokenizer, example[\"text\"], question, bos=tokenizer.bos_token),\n", " \"completion\": option_codes[example[\"label_text\"]] + tokenizer.eos_token,\n", " }\n", "\n", "# Each example gets one option list, which is enough for one epoch. To train for more epochs, map the dataset\n", "# once per epoch with a different `split` value and concatenate the copies, so every pass sees new lists.\n", "train_dataset_sft = sft_train.map(to_prompt_completion, with_indices=True, fn_kwargs={\"split\": \"train\"}, remove_columns=sft_train.column_names)\n", "eval_dataset_sft = sft_eval.map(to_prompt_completion, with_indices=True, fn_kwargs={\"split\": \"eval\"}, remove_columns=sft_eval.column_names)\n", "\n", "print(\"โ SFT Dataset formatted:\")\n", "print(f\" ๐ Train samples: {len(train_dataset_sft)}\")\n", "print(f\" ๐งช Eval samples: {len(eval_dataset_sft)}\")\n", "print(f\"\\n๐ Prompt:\\n{train_dataset_sft[0]['prompt']}\")\n", "print(f\"\\n๐ฏ Completion: {train_dataset_sft[0]['completion']!r}\")" ] }, { "cell_type": "markdown", "metadata": { "id": "2851ff7ac170" }, "source": [ "Let's look at two training messages with the same intent. Each prompt has its own option list, so `card arrival` gets a different code in each, and the model has to read the list to answer." ] }, { "cell_type": "code", "execution_count": 10, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "f17deaf926d0", "outputId": "4317b465-1e6f-4441-9e9e-29488ca59ba1" }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "๐ฌ Message: Where is the card I ordered 2 weeks ago?\n", "๐ข Number of options: 76\n", "๐ First options: ['00 get disposable virtual card', '01 edit personal details', '02 getting virtual card']\n", "๐ Correct option: 08 card arrival\n", "๐ฏ Completion: '08<|im_end|>'\n", "\n", "๐ฌ Message: Can I track when my card will be delivered?\n", "๐ข Number of options: 69\n", "๐ First options: ['00 activate my card', '01 getting spare card', '02 getting virtual card']\n", "๐ Correct option: 55 card arrival\n", "๐ฏ Completion: '55<|im_end|>'\n", "\n" ] } ], "source": [ "import re\n", "\n", "# The first two training messages whose intent is card_arrival\n", "for i in [i for i, label in enumerate(sft_train[\"label_text\"]) if label == \"card_arrival\"][:2]:\n", " options = re.findall(r\"^\\d\\d .+$\", train_dataset_sft[i][\"prompt\"], re.M)\n", " print(f\"๐ฌ Message: {sft_train[i]['text']}\")\n", " print(f\"๐ข Number of options: {len(options)}\")\n", " print(f\"๐ First options: {options[:3]}\")\n", " print(f\"๐ Correct option: {next(option for option in options if option.endswith(' card arrival'))}\")\n", " print(f\"๐ฏ Completion: {train_dataset_sft[i]['completion']!r}\\n\")" ] }, { "cell_type": "markdown", "metadata": { "id": "oYoj310Y_0b0" }, "source": [ "## Evaluate the base model\n", "\n", "Let's measure how well d1-3B performs on the 100 test messages before fine-tuning. `system_one_batch` packs many messages into each forward pass." ] }, { "cell_type": "code", "execution_count": 11, "metadata": { "colab": { "base_uri": "https://localhost:8080/", "height": 83, "referenced_widgets": [ "d3c579abbe4241309cdfca6aa6144ed0", "345ae6331c5843e188fa61ce6155eceb", "c3d88beb07b14fbcb244cb85784b1564", "e9d1be613c9443e7a960dd4ba0645173", "078b55e068cc46f28637437e2f26bd96", "a322bb64fbcb4566aab6abe9ebad46f8", "a24a84128b734f24963d5b34fce121c7", "587dc47fea5c412b9ec8c8741fb2a142", "74b19df3c27547f4ac949ba14d5dfc69", "5b9201c9fbb043449fb43142d3cc0aee", "5a3c7e1a7e914dfe8dcd30f5ffc0efe3" ] }, "id": "0-uTPjYo_0b0", "outputId": "74471886-ff45-4f55-b805-5af1a4cd55bb" }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "๐ Evaluating the base model...\n" ] }, { "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "d3c579abbe4241309cdfca6aa6144ed0", "version_major": 2, "version_minor": 0 }, "text/plain": [ "Evaluating: 0%| | 0/7 [00:00, ?it/s]" ] }, "metadata": {}, "output_type": "display_data" }, { "name": "stdout", "output_type": "stream", "text": [ "๐ฏ Baseline accuracy: 83.0%\n" ] } ], "source": [ "from tqdm.auto import tqdm\n", "\n", "def evaluate(model, dataset, batch_size=16):\n", " \"\"\"Accuracy of the model's intent decisions on `dataset`, and its predictions.\"\"\"\n", " predictions = []\n", " # Evaluate in batches, so the progress bar shows how far we are\n", " for start in tqdm(range(0, len(dataset), batch_size), desc=\"Evaluating\"):\n", " texts = dataset[start:start + batch_size][\"text\"]\n", " responses = model.system_one_batch([(text, {\"intent\": intent_question}) for text in texts])\n", " predictions += [response[\"answers\"][\"intent\"][\"choice\"] for response in responses]\n", " accuracy = sum(p == y for p, y in zip(predictions, dataset[\"label_text\"])) / len(dataset)\n", " return accuracy, predictions\n", "\n", "print(\"๐ Evaluating the base model...\")\n", "baseline_accuracy, baseline_predictions = evaluate(model, test_dataset)\n", "print(f\"๐ฏ Baseline accuracy: {baseline_accuracy:.1%}\")" ] }, { "cell_type": "markdown", "metadata": { "id": "s_wTH9G9_0b1" }, "source": [ "Let's free up the GPU memory before training:" ] }, { "cell_type": "code", "execution_count": 12, "metadata": { "id": "9imKBbWL_0b1" }, "outputs": [], "source": [ "import gc\n", "\n", "del model\n", "gc.collect()\n", "torch.cuda.empty_cache()" ] }, { "cell_type": "markdown", "metadata": { "id": "ruXWk7Jv_0b1" }, "source": [ "## Load the model for training\n", "\n", "For training, we load the same weights with the standard Transformers class. d1-3B's checkpoint uses the standard LFM2-VL module names, so we don't need its custom code here." ] }, { "cell_type": "code", "execution_count": 13, "metadata": { "colab": { "base_uri": "https://localhost:8080/", "height": 83, "referenced_widgets": [ "6e73504339c548bbb0e20b68aa70efac", "4199e28e303a4b6bbe5e187630a76d47", "bbc795e0af934a7eb9512a3075d93c68", "5b01c37198574076b1f5f8fa76c32901", "dd918eaf3dfa4ba998bb17d006b376f5", "ee8de16656484960b2d407e925545443", "7fb3d0b04fdf4bb7a2992c4016d4ffb6", "691a0080e59a408c8bc8cd5aed492d0d", "e7790ad1f61143778d2c6142717daddf", "66647c91240b4e49bdb4a7eb1a2f4cd1", "14c380de83cd45a5acdd09926ee55ebd" ] }, "id": "vx0KQiKc_0b1", "outputId": "add893d5-43cf-4041-f754-1842f5589853" }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "๐ง Loading model for training...\n" ] }, { "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "6e73504339c548bbb0e20b68aa70efac", "version_major": 2, "version_minor": 0 }, "text/plain": [ "Loading weights: 0%| | 0/707 [00:00, ?it/s]" ] }, "metadata": {}, "output_type": "display_data" }, { "name": "stdout", "output_type": "stream", "text": [ "โ Model loaded successfully!\n" ] } ], "source": [ "from transformers import AutoModelForImageTextToText\n", "\n", "print(\"๐ง Loading model for training...\")\n", "model = AutoModelForImageTextToText.from_pretrained(model_id, dtype=dtype, device_map=\"auto\")\n", "print(\"โ Model loaded successfully!\")" ] }, { "cell_type": "markdown", "metadata": { "id": "X0gu5_f6_0b1" }, "source": [ "## Wrap the model with PEFT\n", "\n", "We specify target modules that will be finetuned while the rest of the models weights remains frozen. Feel free to modify the `r` (rank) value:\n", "- Higher โ better approximation of full-finetuning\n", "- Lower โ needs even less compute resources\n", "\n", "Our task is text-only, so we only adapt the language model: its attention layers (`q_proj`, `k_proj`, `v_proj`, `out_proj`), its short convolution layers (`in_proj`, `out_proj`), and its MLP layers (`w1`, `w2`, `w3`). The vision encoder uses some of the same layer names, so we exclude it explicitly." ] }, { "cell_type": "code", "execution_count": 14, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "ADwbfFWA_0b1", "outputId": "35fe9250-0667-4ee3-96ad-9dff8a86a706" }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "trainable params: 24,461,312 || all params: 3,147,945,200 || trainable%: 0.7771\n", "โ LoRA configuration created!\n", "๐๏ธ LoRA rank: 16\n", "๐ LoRA alpha: 32\n" ] } ], "source": [ "from peft import LoraConfig, get_peft_model\n", "\n", "target_modules = [\n", " \"q_proj\", \"k_proj\", \"v_proj\",\n", " \"out_proj\", \"in_proj\",\n", " \"w1\", \"w2\", \"w3\"\n", "]\n", "\n", "peft_config = LoraConfig(\n", " r=16, # <- lower values = fewer parameters\n", " lora_alpha=32,\n", " lora_dropout=0.05,\n", " bias=\"none\",\n", " task_type=\"CAUSAL_LM\",\n", " target_modules=target_modules,\n", " exclude_modules=r\".*vision_tower.*\", # The vision encoder also has q_proj, k_proj, v_proj, and out_proj layers: keep them frozen by excluding them\n", ")\n", "\n", "model = get_peft_model(model, peft_config)\n", "model.print_trainable_parameters()\n", "\n", "print(\"โ LoRA configuration created!\")\n", "print(f\"๐๏ธ LoRA rank: {peft_config.r}\")\n", "print(f\"๐ LoRA alpha: {peft_config.lora_alpha}\")" ] }, { "cell_type": "markdown", "metadata": { "id": "f7nwtzbY_0b1" }, "source": [ "## Launch Training\n", "\n", "We are now ready to launch an SFT run with `SFTTrainer`, feel free to modify `SFTConfig` to play around with different configurations." ] }, { "cell_type": "code", "execution_count": 15, "metadata": { "colab": { "base_uri": "https://localhost:8080/", "height": 641, "referenced_widgets": [ "3e2829a6d5d34e6191b4d0f0b2d1c341", "18fb25ce7f7b48ae87bb6556d69c1770", "82245cfff8cb4c868f3e33da79ecfe63", "b3144826b13f4af7bb5a0cac49cddb6d", "6c0d9f084e8748f6b17ba8e8db2b5779", "15b9d60119f04b359ae91b1fec2b6431", "e405de1a64ec46fd9e93ffb959cac0fc", "7fb11d98c0a248b9a73dea8dc245fa0b", "e2ae875127154f16b1578ccf1bf42053", "675e51ac6d4443ce947e679d71a15d17", "108af22082104a23be56d725a1fc8ca6", "487280cb4fc14c0faa70518f83ba7935", "38c0417f9216461ca7cb7634262b836d", "179ab35d4d3e44beb79bc27046fce0f8", "2e303f7e9dc9466998d7aa54ac20d45b", "8f1984fa66aa4a249f3d66d831867617", "1314c07915c3492a9d6bff07480fffc9", "0874f81d0824465c9c75ca885c3f49f0", "1d72514a58b34afb94e79f2cf87a5d85", "76b91532362c4a7bbe5cee04dae7b350", "7c910ecb322549e58c76a76649496b9b", "857125b0a53f4c6fb4a16963639064f3", "075e98c68a764003a54223bc49211db0", "645bf442a0e145cdb28a07cd8f9626b2", "6cd61d4f11374a1ca4c7394c24fc67e2", "bc26e61eb7e8416cb447b7596b4c684d", "f73e1740a75240e88e679fe01c363cfb", "cfa415404c484c70ac0c58fb0ce0a136", "03cf72426df2471a92436f46e1d2c5f1", "74711037f8274ad49687297bf2d5cd82", "9c605cc3d34f4c31ac5639d1b1ea0d68", "97373297d390499a88a96555d890b3a1", "06653cc3b2dc49cdad5d855e254701e1", "565533ab6ed44869be52d8347d219834", "5b42fe90d03d4f7f8164670655e9cc19", "fb8a167ac33f41ccac49fa7b91d1dc41", "c24b83da4a544887b3115dddec3e8dea", "5301f5c30e4445c095e77c48012fd350", "5f96b2aa1f8f4aeda2cb32c8f85b8e4c", "16a1089ff5824412942e15a9fd310198", "de5fc28b6e1a4443a5b008d8d81cbc4f", "bd87729a5fe84645aa10ce14dd2568b5", "a1ef54e3c6e8416da90df3f907ce3c50", "d5b1dd2912ee4594b32771d66b6879ed", "782d01978c904ae6bee9ad740b21684e", "ca7d594ed1f84c5eabb79a0cc93f9c16", "48d02f653fd7412394f5ce56e538c48d", "fa94292c2c894a989b3922c1423821a0", "458306d6ccc949339631b8a3cd50968d", "eb1f4b16a7584d55a50ef0bc52a24b97", "c3d99147112249e78b4ee794428d9420", "2604050588284af88d7cf6ab6a7eabda", "ec60c45e57164f619237ee60e13cb5d1", "22e9ead14530410bbea68eb26cd957e9", "2cd44bccecd946d7b80f4e42dc1bff19", "e66f49ba5df74cadb64ae61ee272e0e8", "ee7b2a8b6028416fb05eff62713eb978", "56e9a2c2f0b043b7b0a451cee41c13c4", "715150f49f5d4678b8a9c3426c5e6430", "627752bbe81249c782b9d7f6ee42d367", "48069881deee444599d65f61bad34f9c", "94e74e882beb49128bfec0d79fadfd4f", "b0e41311a71d4a52bb340f74b8300dcb", "0b6425ccb92142c9b550b48946599031", "67576ca225d44c6784fc9587f1f46ad7", "c2181711628248b68a96fed34c485d01", "62a19460088043e89960fe28a44c8898", "4dcb80cc82bd438b8b10588474758afa", "d854d7ea3bfe41a9b8f894612e4d5127", "39fc363757944b32903948bddd2efe8d", "14d99e4d8b3b40b392533b2af4a7b54b", "183a122fdbd4439b983eb1d675196060", "d7585bec07334aafa4e6d45c9f94e10d", "20a74e3625b4467fa834580da3788233", "4e82ee9ba61f4836bf686e16aa391fe9", "97d7af5ac8cd447393c578fba1dcbc47", "ecdce5ec80764f78bc33217ad751637c", "677de2d9e486411092b397a0bfea38a1", "9e5d9fa678d04b42abab0ac8721c9a13", "afd3598c8aa24686b5975b5aba0cdc4c", "e60f8385af0749a88d8d0f935788fb7a", "8afe6be8ddd34073938fc3a608c3cc48", "8ddbb6a824b34a1aa267c34c69917b77", "b0a8b0f80c784eeba3ba7cc0c818d892", "30413d6552a74fdaafe1b54683786de2", "f08b748a1efa4b418a6898cb12fab7e7", "bb31ef03be5141e7920aac25fa6ef6d5", "ed3e797c97c54f77873d3a89d3e921ef", "e43cfb4506d54fbab340e3c3ca494a09", "5824c05fe6e84584b7fe29975740dcec", "c1c4d11bd3d04ba6a8c2f0b51a567ddb", "ea84511a4f5d4dd086c499063d2c4a79", "6b4c78901eb54c58b76e34c35b045a78", "81cdf0fc7c0d449799f5fbdf3e25ae6c", "4e61743f46014261999ad6a7aa5eed39", "2399c108277a457ea287a3992ac9f520", "ff58d7dad76a46e3968ed3a9881f5c9e", "e54af6cb6c994d6d85a8c639962da4b3", "efcdf372468f4b3b850967571433b93c", "8c18be6caf494c57806af465e1689944", "4192caee78734fbb9440b86a476528b4", "6f17d3622cae4533b27208dc63153678", "e164785fbc3c463494dbb80298e6ebe7", "aea8fbd4684a4940818acb100b4101dc", "f465c38f2c7444119310c6b74b765216", "184e5247034242e6bef05f68e141b75e", "0a045553910047bd9ac077da55216368", "8751b1182efd44deb9d2727f4bd7766e", "2f225a3b7ec64a378aa45a73de456f57", "3e317d6b678f49f9b6460e5045d831db" ] }, "id": "M2yFhUB7_0b1", "outputId": "6ba700c3-8560-4b5e-edb7-04c26ce61a24" }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "๐๏ธ Creating SFT trainer...\n" ] }, { "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "3e2829a6d5d34e6191b4d0f0b2d1c341", "version_major": 2, "version_minor": 0 }, "text/plain": [ "Adding EOS to train dataset: 0%| | 0/2694 [00:00, ? examples/s]" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "487280cb4fc14c0faa70518f83ba7935", "version_major": 2, "version_minor": 0 }, "text/plain": [ "Tokenizing train dataset: 0%| | 0/2694 [00:00, ? examples/s]" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "075e98c68a764003a54223bc49211db0", "version_major": 2, "version_minor": 0 }, "text/plain": [ "Building labels for train dataset: 0%| | 0/2694 [00:00, ? examples/s]" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "565533ab6ed44869be52d8347d219834", "version_major": 2, "version_minor": 0 }, "text/plain": [ "Truncating train dataset: 0%| | 0/2694 [00:00, ? examples/s]" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "782d01978c904ae6bee9ad740b21684e", "version_major": 2, "version_minor": 0 }, "text/plain": [ "Dropping fully masked examples from train dataset: 0%| | 0/2694 [00:00, ? examples/s]" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "e66f49ba5df74cadb64ae61ee272e0e8", "version_major": 2, "version_minor": 0 }, "text/plain": [ "Adding EOS to eval dataset: 0%| | 0/300 [00:00, ? examples/s]" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "62a19460088043e89960fe28a44c8898", "version_major": 2, "version_minor": 0 }, "text/plain": [ "Tokenizing eval dataset: 0%| | 0/300 [00:00, ? examples/s]" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "677de2d9e486411092b397a0bfea38a1", "version_major": 2, "version_minor": 0 }, "text/plain": [ "Building labels for eval dataset: 0%| | 0/300 [00:00, ? examples/s]" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "e43cfb4506d54fbab340e3c3ca494a09", "version_major": 2, "version_minor": 0 }, "text/plain": [ "Truncating eval dataset: 0%| | 0/300 [00:00, ? examples/s]" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "8c18be6caf494c57806af465e1689944", "version_major": 2, "version_minor": 0 }, "text/plain": [ "Dropping fully masked examples from eval dataset: 0%| | 0/300 [00:00, ? examples/s]" ] }, "metadata": {}, "output_type": "display_data" }, { "name": "stdout", "output_type": "stream", "text": [ "\n", "๐ Starting SFT training...\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ "[transformers] `causal_conv1d_fn` is falling back to its reference PyTorch implementation because `causal_conv1d` is not installed. This is correct but much slower; install `causal_conv1d` for the optimized kernel.\n" ] }, { "data": { "text/html": [ "\n", "
| Step | \n", "Training Loss | \n", "Validation Loss | \n", "Entropy | \n", "Mean Token Accuracy | \n", "Num Tokens | \n", "
|---|---|---|---|---|---|
| 50 | \n", "0.180615 | \n", "0.226280 | \n", "0.178536 | \n", "0.934211 | \n", "264665.000000 | \n", "
| 100 | \n", "0.149379 | \n", "0.170480 | \n", "0.106043 | \n", "0.957237 | \n", "529059.000000 | \n", "
| 150 | \n", "0.097676 | \n", "0.149993 | \n", "0.151665 | \n", "0.958882 | \n", "793472.000000 | \n", "
| 169 | \n", "0.185679 | \n", "0.149433 | \n", "0.152403 | \n", "0.958882 | \n", "888013.000000 | \n", "
"
],
"text/plain": [
"