Spaces:
Runtime error
Runtime error
FreakForever commited on
Commit Β·
ccc4a28
1
Parent(s): 7bc4937
initiated the base structure for blog and added the required images
Browse files
notebooks/CFO-RL-Train.ipynb
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
notebooks/CFO-SFT-Training-Llama.ipynb
ADDED
|
@@ -0,0 +1,863 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"cells": [
|
| 3 |
+
{
|
| 4 |
+
"cell_type": "markdown",
|
| 5 |
+
"metadata": {},
|
| 6 |
+
"source": [
|
| 7 |
+
"# π¦ CFO Agent β SFT Training (Gemma 2 9B)\n",
|
| 8 |
+
"\n",
|
| 9 |
+
"Single-agent **Supervised Fine-Tuning** for the CFO decision-maker. \n",
|
| 10 |
+
"The other three agents (Expenditure, Revenue, Risk) are handled via ICL separately.\n",
|
| 11 |
+
"\n",
|
| 12 |
+
"**Base Model**: `unsloth/gemma-2-9b-it-bnb-4bit` \n",
|
| 13 |
+
"**Method**: QLoRA (4-bit) with Unsloth \n",
|
| 14 |
+
"**Hardware**: β₯24 GB VRAM recommended (A100/H100/L4)\n"
|
| 15 |
+
]
|
| 16 |
+
},
|
| 17 |
+
{
|
| 18 |
+
"cell_type": "markdown",
|
| 19 |
+
"metadata": {},
|
| 20 |
+
"source": [
|
| 21 |
+
"## 1. Install Dependencies"
|
| 22 |
+
]
|
| 23 |
+
},
|
| 24 |
+
{
|
| 25 |
+
"cell_type": "code",
|
| 26 |
+
"execution_count": 1,
|
| 27 |
+
"id": "6b0bce92",
|
| 28 |
+
"metadata": {},
|
| 29 |
+
"outputs": [],
|
| 30 |
+
"source": [
|
| 31 |
+
"!pip install -q \"unsloth[colab-new] @ git+https://github.com/unslothai/unsloth.git\" \\\n",
|
| 32 |
+
" datasets trl matplotlib pandas huggingface_hub python-dotenv\n"
|
| 33 |
+
]
|
| 34 |
+
},
|
| 35 |
+
{
|
| 36 |
+
"cell_type": "markdown",
|
| 37 |
+
"metadata": {},
|
| 38 |
+
"source": [
|
| 39 |
+
"## 2. Configuration"
|
| 40 |
+
]
|
| 41 |
+
},
|
| 42 |
+
{
|
| 43 |
+
"cell_type": "code",
|
| 44 |
+
"execution_count": null,
|
| 45 |
+
"metadata": {},
|
| 46 |
+
"outputs": [
|
| 47 |
+
{
|
| 48 |
+
"name": "stderr",
|
| 49 |
+
"output_type": "stream",
|
| 50 |
+
"text": [
|
| 51 |
+
"Note: Environment variable`HF_TOKEN` is set and is the current active token independently from the token you've just configured.\n"
|
| 52 |
+
]
|
| 53 |
+
},
|
| 54 |
+
{
|
| 55 |
+
"name": "stdout",
|
| 56 |
+
"output_type": "stream",
|
| 57 |
+
"text": [
|
| 58 |
+
"GPU : Tesla T4\n",
|
| 59 |
+
"VRAM : 15.6 GB\n",
|
| 60 |
+
"Model: unsloth/Llama-3.2-3B-Instruct-bnb-4bit\n",
|
| 61 |
+
"Data : /teamspace/studios/this_studio/Cashflowmanager/data/cfo_sft.jsonl\n"
|
| 62 |
+
]
|
| 63 |
+
}
|
| 64 |
+
],
|
| 65 |
+
"source": [
|
| 66 |
+
"import os, json, inspect, torch\n",
|
| 67 |
+
"import matplotlib.pyplot as plt\n",
|
| 68 |
+
"from datasets import Dataset\n",
|
| 69 |
+
"from huggingface_hub import login\n",
|
| 70 |
+
"from dotenv import load_dotenv\n",
|
| 71 |
+
"os.environ[\"TORCH_COMPILE_DISABLE\"] = \"1\" # β
Ensure Dynamo crashes are prevented\n",
|
| 72 |
+
"load_dotenv()\n",
|
| 73 |
+
"HF_TOKEN = os.environ.get(\"HF_TOKEN\", \"\")\n",
|
| 74 |
+
"if HF_TOKEN:\n",
|
| 75 |
+
" login(token=HF_TOKEN)\n",
|
| 76 |
+
"# ββ Changed from Gemma to Llama 3.2 (T4 Compatible) ββ\n",
|
| 77 |
+
"MODEL_NAME = \"unsloth/Llama-3.2-3B-Instruct-bnb-4bit\"\n",
|
| 78 |
+
"# ββ Important: Must use Llama's specific chat template ββ\n",
|
| 79 |
+
"CHAT_TEMPLATE = \"llama-3.1\"\n",
|
| 80 |
+
"MAX_SEQ_LENGTH = 2048\n",
|
| 81 |
+
"LORA_R = 32\n",
|
| 82 |
+
"LORA_ALPHA = 32\n",
|
| 83 |
+
"BATCH_SIZE = 2 \n",
|
| 84 |
+
"GRAD_ACCUM = 4\n",
|
| 85 |
+
"MAX_STEPS = 100\n",
|
| 86 |
+
"LEARNING_RATE = 1e-4\n",
|
| 87 |
+
"WARMUP_RATIO = 0.05\n",
|
| 88 |
+
"WEIGHT_DECAY = 0.01\n",
|
| 89 |
+
"BASE_PATH = \"/teamspace/studios/this_studio/Cashflowmanager\"\n",
|
| 90 |
+
"DATA_PATH = f\"{BASE_PATH}/data/cfo_sft.jsonl\"\n",
|
| 91 |
+
"OUTPUT_DIR = \"outputs/cfo\"\n",
|
| 92 |
+
"os.makedirs(OUTPUT_DIR, exist_ok=True)\n",
|
| 93 |
+
"device = \"cuda\" if torch.cuda.is_available() else \"cpu\"\n",
|
| 94 |
+
"print(f\"GPU : {torch.cuda.get_device_name(0) if device == 'cuda' else 'CPU'}\")\n",
|
| 95 |
+
"print(f\"VRAM : {torch.cuda.get_device_properties(0).total_memory / 1e9:.1f} GB\" if device == \"cuda\" else \"\")\n",
|
| 96 |
+
"print(f\"Model: {MODEL_NAME}\")\n",
|
| 97 |
+
"print(f\"Data : {DATA_PATH}\")\n"
|
| 98 |
+
]
|
| 99 |
+
},
|
| 100 |
+
{
|
| 101 |
+
"cell_type": "markdown",
|
| 102 |
+
"metadata": {},
|
| 103 |
+
"source": [
|
| 104 |
+
"## 3. Inspect CFO Training Data"
|
| 105 |
+
]
|
| 106 |
+
},
|
| 107 |
+
{
|
| 108 |
+
"cell_type": "code",
|
| 109 |
+
"execution_count": 3,
|
| 110 |
+
"metadata": {},
|
| 111 |
+
"outputs": [
|
| 112 |
+
{
|
| 113 |
+
"name": "stdout",
|
| 114 |
+
"output_type": "stream",
|
| 115 |
+
"text": [
|
| 116 |
+
"Total samples: 3000\n",
|
| 117 |
+
"\n",
|
| 118 |
+
"============================================================\n",
|
| 119 |
+
"System prompt:\n",
|
| 120 |
+
"You are the CFO of a company managing daily cash flow decisions.\n",
|
| 121 |
+
"You receive memos from three advisors:\n",
|
| 122 |
+
"- Expenditure Agent: payment priorities\n",
|
| 123 |
+
"- Revenue Agent: cash inflow projections\n",
|
| 124 |
+
"- Risk Agent: threat assessment\n",
|
| 125 |
+
"\n",
|
| 126 |
+
"Based on their advice and the current financial state, decide the action for each invoice.\n",
|
| 127 |
+
"\n",
|
| 128 |
+
"You must output a JSON object with:\n",
|
| 129 |
+
"- \"actions\": list of {\"invoice_id\": \"...\", \"type\": \"pay|defer|partial|negotiate|credit\", \"amount\": float, \"reasoning\": \"...\"}\n",
|
| 130 |
+
"- \"overall_strategy\": 1-2 sentence explanation of your approach\n",
|
| 131 |
+
"- \"confidence\": float 0.0-1.0\n",
|
| 132 |
+
"\n",
|
| 133 |
+
"Rules:\n",
|
| 134 |
+
"- You CANNOT spend more cash than available (cash + remaining credit)\n",
|
| 135 |
+
"- Balance between paying urgent invoices and maintaining reserves\n",
|
| 136 |
+
"- Use negotiate when vendor trust is high and you need relief\n",
|
| 137 |
+
"- Use credit only when necessary β it reduces your score\n",
|
| 138 |
+
"- Consider advisor warnings seriously\n",
|
| 139 |
+
"\n",
|
| 140 |
+
"ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ\n",
|
| 141 |
+
"User input (first 300 chars):\n",
|
| 142 |
+
"Day: 1 | Cash: βΉ720441 | Credit: βΉ0/500000\n",
|
| 143 |
+
"\n",
|
| 144 |
+
"ADVISORS:\n",
|
| 145 |
+
"[Expenditure]: [PAY_IMMEDIATELY] Total Debt: βΉ1590422. Top Priority: 1c0e6ba7. Invoice 1c0e6ba7 has a high penalty cost (βΉ17121). Pay immediately.\n",
|
| 146 |
+
"[Revenue]: [CASH_CRITICAL] Inflow: βΉ1207790. Net Position: βΉ-271485. Economic stress (0.3) is impac\n",
|
| 147 |
+
"\n",
|
| 148 |
+
"ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ\n",
|
| 149 |
+
"Expected output:\n",
|
| 150 |
+
"{\n",
|
| 151 |
+
" \"type\": \"pay\",\n",
|
| 152 |
+
" \"invoice_id\": \"1c0e6ba7\",\n",
|
| 153 |
+
" \"amount\": 107262.01799102609,\n",
|
| 154 |
+
" \"reasoning\": \"Paying critical invoice 1c0e6ba7 to avoid penalties\"\n",
|
| 155 |
+
"}\n"
|
| 156 |
+
]
|
| 157 |
+
}
|
| 158 |
+
],
|
| 159 |
+
"source": [
|
| 160 |
+
"with open(DATA_PATH) as f:\n",
|
| 161 |
+
" lines = f.readlines()\n",
|
| 162 |
+
"\n",
|
| 163 |
+
"print(f\"Total samples: {len(lines)}\")\n",
|
| 164 |
+
"\n",
|
| 165 |
+
"sample = json.loads(lines[0])\n",
|
| 166 |
+
"print(f'\\n{\"=\"*60}')\n",
|
| 167 |
+
"print(\"System prompt:\")\n",
|
| 168 |
+
"print(sample[\"messages\"][0][\"content\"])\n",
|
| 169 |
+
"print(f'\\n{\"β\"*60}')\n",
|
| 170 |
+
"print(\"User input (first 300 chars):\")\n",
|
| 171 |
+
"print(sample[\"messages\"][1][\"content\"][:300])\n",
|
| 172 |
+
"print(f'\\n{\"β\"*60}')\n",
|
| 173 |
+
"print(\"Expected output:\")\n",
|
| 174 |
+
"print(sample[\"messages\"][2][\"content\"])\n"
|
| 175 |
+
]
|
| 176 |
+
},
|
| 177 |
+
{
|
| 178 |
+
"cell_type": "markdown",
|
| 179 |
+
"metadata": {},
|
| 180 |
+
"source": [
|
| 181 |
+
"## 4. Train CFO Agent"
|
| 182 |
+
]
|
| 183 |
+
},
|
| 184 |
+
{
|
| 185 |
+
"cell_type": "code",
|
| 186 |
+
"execution_count": 4,
|
| 187 |
+
"metadata": {},
|
| 188 |
+
"outputs": [
|
| 189 |
+
{
|
| 190 |
+
"name": "stdout",
|
| 191 |
+
"output_type": "stream",
|
| 192 |
+
"text": [
|
| 193 |
+
"π¦₯ Unsloth: Will patch your computer to enable 2x faster free finetuning.\n",
|
| 194 |
+
"π¦₯ Unsloth Zoo will now patch everything to make training faster!\n",
|
| 195 |
+
"==((====))== Unsloth 2026.4.8: Fast Llama patching. Transformers: 5.5.0.\n",
|
| 196 |
+
" \\\\ /| Tesla T4. Num GPUs = 1. Max memory: 14.562 GB. Platform: Linux.\n",
|
| 197 |
+
"O^O/ \\_/ \\ Torch: 2.8.0+cu128. CUDA: 7.5. CUDA Toolkit: 12.8. Triton: 3.4.0\n",
|
| 198 |
+
"\\ / Bfloat16 = FALSE. FA [Xformers = None. FA2 = False]\n",
|
| 199 |
+
" \"-____-\" Free license: http://github.com/unslothai/unsloth\n",
|
| 200 |
+
"Unsloth: Fast downloading is enabled - ignore downloading bars which are red colored!\n"
|
| 201 |
+
]
|
| 202 |
+
},
|
| 203 |
+
{
|
| 204 |
+
"data": {
|
| 205 |
+
"application/vnd.jupyter.widget-view+json": {
|
| 206 |
+
"model_id": "e07b9a1a1c6d4126b472332274c39901",
|
| 207 |
+
"version_major": 2,
|
| 208 |
+
"version_minor": 0
|
| 209 |
+
},
|
| 210 |
+
"text/plain": [
|
| 211 |
+
"Loading weights: 0%| | 0/254 [00:00<?, ?it/s]"
|
| 212 |
+
]
|
| 213 |
+
},
|
| 214 |
+
"metadata": {},
|
| 215 |
+
"output_type": "display_data"
|
| 216 |
+
},
|
| 217 |
+
{
|
| 218 |
+
"name": "stderr",
|
| 219 |
+
"output_type": "stream",
|
| 220 |
+
"text": [
|
| 221 |
+
"Unsloth: Will load unsloth/Llama-3.2-3B-Instruct-bnb-4bit as a legacy tokenizer.\n",
|
| 222 |
+
"Unsloth 2026.4.8 patched 28 layers with 28 QKV layers, 28 O layers and 28 MLP layers.\n"
|
| 223 |
+
]
|
| 224 |
+
},
|
| 225 |
+
{
|
| 226 |
+
"name": "stdout",
|
| 227 |
+
"output_type": "stream",
|
| 228 |
+
"text": [
|
| 229 |
+
"Loaded 3000 CFO samples\n"
|
| 230 |
+
]
|
| 231 |
+
},
|
| 232 |
+
{
|
| 233 |
+
"data": {
|
| 234 |
+
"application/vnd.jupyter.widget-view+json": {
|
| 235 |
+
"model_id": "1a4ec836a08c422aa10273951a235144",
|
| 236 |
+
"version_major": 2,
|
| 237 |
+
"version_minor": 0
|
| 238 |
+
},
|
| 239 |
+
"text/plain": [
|
| 240 |
+
"Map: 0%| | 0/3000 [00:00<?, ? examples/s]"
|
| 241 |
+
]
|
| 242 |
+
},
|
| 243 |
+
"metadata": {},
|
| 244 |
+
"output_type": "display_data"
|
| 245 |
+
},
|
| 246 |
+
{
|
| 247 |
+
"name": "stderr",
|
| 248 |
+
"output_type": "stream",
|
| 249 |
+
"text": [
|
| 250 |
+
"warmup_ratio is deprecated and will be removed in v5.2. Use `warmup_steps` instead.\n"
|
| 251 |
+
]
|
| 252 |
+
},
|
| 253 |
+
{
|
| 254 |
+
"data": {
|
| 255 |
+
"application/vnd.jupyter.widget-view+json": {
|
| 256 |
+
"model_id": "9e6026324f5f44e29481219d7cf96477",
|
| 257 |
+
"version_major": 2,
|
| 258 |
+
"version_minor": 0
|
| 259 |
+
},
|
| 260 |
+
"text/plain": [
|
| 261 |
+
"Unsloth: Tokenizing [\"text\"] (num_proc=2): 0%| | 0/3000 [00:00<?, ? examples/s]"
|
| 262 |
+
]
|
| 263 |
+
},
|
| 264 |
+
"metadata": {},
|
| 265 |
+
"output_type": "display_data"
|
| 266 |
+
},
|
| 267 |
+
{
|
| 268 |
+
"name": "stdout",
|
| 269 |
+
"output_type": "stream",
|
| 270 |
+
"text": [
|
| 271 |
+
"π¦₯ Unsloth: Padding-free auto-enabled, enabling faster training.\n",
|
| 272 |
+
"\n",
|
| 273 |
+
"Training CFO agent on unsloth/Llama-3.2-3B-Instruct-bnb-4bit ...\n"
|
| 274 |
+
]
|
| 275 |
+
},
|
| 276 |
+
{
|
| 277 |
+
"name": "stderr",
|
| 278 |
+
"output_type": "stream",
|
| 279 |
+
"text": [
|
| 280 |
+
"==((====))== Unsloth - 2x faster free finetuning | Num GPUs used = 1\n",
|
| 281 |
+
" \\\\ /| Num examples = 3,000 | Num Epochs = 1 | Total steps = 50\n",
|
| 282 |
+
"O^O/ \\_/ \\ Batch size per device = 2 | Gradient accumulation steps = 4\n",
|
| 283 |
+
"\\ / Data Parallel GPUs = 1 | Total batch size (2 x 4 x 1) = 8\n",
|
| 284 |
+
" \"-____-\" Trainable parameters = 48,627,712 of 3,261,377,536 (1.49% trained)\n",
|
| 285 |
+
"`use_return_dict` is deprecated! Use `return_dict` instead!\n"
|
| 286 |
+
]
|
| 287 |
+
},
|
| 288 |
+
{
|
| 289 |
+
"name": "stdout",
|
| 290 |
+
"output_type": "stream",
|
| 291 |
+
"text": [
|
| 292 |
+
"Unsloth: Will smartly offload gradients to save VRAM!\n"
|
| 293 |
+
]
|
| 294 |
+
},
|
| 295 |
+
{
|
| 296 |
+
"data": {
|
| 297 |
+
"text/html": [
|
| 298 |
+
"\n",
|
| 299 |
+
" <div>\n",
|
| 300 |
+
" \n",
|
| 301 |
+
" <progress value='50' max='50' style='width:300px; height:20px; vertical-align: middle;'></progress>\n",
|
| 302 |
+
" [50/50 05:58, Epoch 0/1]\n",
|
| 303 |
+
" </div>\n",
|
| 304 |
+
" <table border=\"1\" class=\"dataframe\">\n",
|
| 305 |
+
" <thead>\n",
|
| 306 |
+
" <tr style=\"text-align: left;\">\n",
|
| 307 |
+
" <th>Step</th>\n",
|
| 308 |
+
" <th>Training Loss</th>\n",
|
| 309 |
+
" </tr>\n",
|
| 310 |
+
" </thead>\n",
|
| 311 |
+
" <tbody>\n",
|
| 312 |
+
" <tr>\n",
|
| 313 |
+
" <td>10</td>\n",
|
| 314 |
+
" <td>2.898653</td>\n",
|
| 315 |
+
" </tr>\n",
|
| 316 |
+
" <tr>\n",
|
| 317 |
+
" <td>20</td>\n",
|
| 318 |
+
" <td>1.335482</td>\n",
|
| 319 |
+
" </tr>\n",
|
| 320 |
+
" <tr>\n",
|
| 321 |
+
" <td>30</td>\n",
|
| 322 |
+
" <td>0.666985</td>\n",
|
| 323 |
+
" </tr>\n",
|
| 324 |
+
" <tr>\n",
|
| 325 |
+
" <td>40</td>\n",
|
| 326 |
+
" <td>0.590089</td>\n",
|
| 327 |
+
" </tr>\n",
|
| 328 |
+
" <tr>\n",
|
| 329 |
+
" <td>50</td>\n",
|
| 330 |
+
" <td>0.565094</td>\n",
|
| 331 |
+
" </tr>\n",
|
| 332 |
+
" </tbody>\n",
|
| 333 |
+
"</table><p>"
|
| 334 |
+
],
|
| 335 |
+
"text/plain": [
|
| 336 |
+
"<IPython.core.display.HTML object>"
|
| 337 |
+
]
|
| 338 |
+
},
|
| 339 |
+
"metadata": {},
|
| 340 |
+
"output_type": "display_data"
|
| 341 |
+
},
|
| 342 |
+
{
|
| 343 |
+
"name": "stderr",
|
| 344 |
+
"output_type": "stream",
|
| 345 |
+
"text": [
|
| 346 |
+
"Unsloth: Restored added_tokens_decoder metadata in outputs/cfo/checkpoint-50/tokenizer_config.json.\n"
|
| 347 |
+
]
|
| 348 |
+
},
|
| 349 |
+
{
|
| 350 |
+
"name": "stdout",
|
| 351 |
+
"output_type": "stream",
|
| 352 |
+
"text": [
|
| 353 |
+
"\n",
|
| 354 |
+
"β
Final loss: 1.2113\n"
|
| 355 |
+
]
|
| 356 |
+
},
|
| 357 |
+
{
|
| 358 |
+
"data": {
|
| 359 |
+
"application/vnd.jupyter.widget-view+json": {
|
| 360 |
+
"model_id": "20be5788a49e4b258b2e00b4b9e9af70",
|
| 361 |
+
"version_major": 2,
|
| 362 |
+
"version_minor": 0
|
| 363 |
+
},
|
| 364 |
+
"text/plain": [
|
| 365 |
+
"config.json: 0%| | 0.00/890 [00:00<?, ?B/s]"
|
| 366 |
+
]
|
| 367 |
+
},
|
| 368 |
+
"metadata": {},
|
| 369 |
+
"output_type": "display_data"
|
| 370 |
+
},
|
| 371 |
+
{
|
| 372 |
+
"name": "stderr",
|
| 373 |
+
"output_type": "stream",
|
| 374 |
+
"text": [
|
| 375 |
+
"Unsloth: Restored added_tokens_decoder metadata in outputs/cfo/tokenizer_config.json.\n"
|
| 376 |
+
]
|
| 377 |
+
},
|
| 378 |
+
{
|
| 379 |
+
"name": "stdout",
|
| 380 |
+
"output_type": "stream",
|
| 381 |
+
"text": [
|
| 382 |
+
"Found HuggingFace hub cache directory: /teamspace/studios/this_studio/.cache/huggingface/hub\n"
|
| 383 |
+
]
|
| 384 |
+
},
|
| 385 |
+
{
|
| 386 |
+
"data": {
|
| 387 |
+
"application/vnd.jupyter.widget-view+json": {
|
| 388 |
+
"model_id": "8e38103cc67245bebaae14832b9b7cda",
|
| 389 |
+
"version_major": 2,
|
| 390 |
+
"version_minor": 0
|
| 391 |
+
},
|
| 392 |
+
"text/plain": [
|
| 393 |
+
"Downloading (incomplete total...): 0.00B [00:00, ?B/s]"
|
| 394 |
+
]
|
| 395 |
+
},
|
| 396 |
+
"metadata": {},
|
| 397 |
+
"output_type": "display_data"
|
| 398 |
+
},
|
| 399 |
+
{
|
| 400 |
+
"data": {
|
| 401 |
+
"application/vnd.jupyter.widget-view+json": {
|
| 402 |
+
"model_id": "21b3c324d5944d60a1ea297899403e52",
|
| 403 |
+
"version_major": 2,
|
| 404 |
+
"version_minor": 0
|
| 405 |
+
},
|
| 406 |
+
"text/plain": [
|
| 407 |
+
"Fetching 1 files: 0%| | 0/1 [00:00<?, ?it/s]"
|
| 408 |
+
]
|
| 409 |
+
},
|
| 410 |
+
"metadata": {},
|
| 411 |
+
"output_type": "display_data"
|
| 412 |
+
},
|
| 413 |
+
{
|
| 414 |
+
"name": "stdout",
|
| 415 |
+
"output_type": "stream",
|
| 416 |
+
"text": [
|
| 417 |
+
"Checking cache directory for required files...\n",
|
| 418 |
+
"Cache check failed: model-00001-of-00002.safetensors not found in local cache.\n",
|
| 419 |
+
"Not all required files found in cache. Will proceed with downloading.\n",
|
| 420 |
+
"Checking cache directory for required files...\n",
|
| 421 |
+
"Cache check failed: tokenizer.model not found in local cache.\n",
|
| 422 |
+
"Not all required files found in cache. Will proceed with downloading.\n"
|
| 423 |
+
]
|
| 424 |
+
},
|
| 425 |
+
{
|
| 426 |
+
"name": "stderr",
|
| 427 |
+
"output_type": "stream",
|
| 428 |
+
"text": []
|
| 429 |
+
},
|
| 430 |
+
{
|
| 431 |
+
"data": {
|
| 432 |
+
"application/vnd.jupyter.widget-view+json": {
|
| 433 |
+
"model_id": "2c3115ba91ad4c6abde45242ecc18972",
|
| 434 |
+
"version_major": 2,
|
| 435 |
+
"version_minor": 0
|
| 436 |
+
},
|
| 437 |
+
"text/plain": [
|
| 438 |
+
"model-00001-of-00002.safetensors: 0%| | 0.00/4.97G [00:00<?, ?B/s]"
|
| 439 |
+
]
|
| 440 |
+
},
|
| 441 |
+
"metadata": {},
|
| 442 |
+
"output_type": "display_data"
|
| 443 |
+
},
|
| 444 |
+
{
|
| 445 |
+
"name": "stderr",
|
| 446 |
+
"output_type": "stream",
|
| 447 |
+
"text": []
|
| 448 |
+
},
|
| 449 |
+
{
|
| 450 |
+
"data": {
|
| 451 |
+
"application/vnd.jupyter.widget-view+json": {
|
| 452 |
+
"model_id": "6d46ec61b03548e98c4323bfe3c8b520",
|
| 453 |
+
"version_major": 2,
|
| 454 |
+
"version_minor": 0
|
| 455 |
+
},
|
| 456 |
+
"text/plain": [
|
| 457 |
+
"model-00002-of-00002.safetensors: 0%| | 0.00/1.46G [00:00<?, ?B/s]"
|
| 458 |
+
]
|
| 459 |
+
},
|
| 460 |
+
"metadata": {},
|
| 461 |
+
"output_type": "display_data"
|
| 462 |
+
},
|
| 463 |
+
{
|
| 464 |
+
"name": "stderr",
|
| 465 |
+
"output_type": "stream",
|
| 466 |
+
"text": [
|
| 467 |
+
"Unsloth: Preparing safetensor model files: 100%|ββββββββββ| 2/2 [00:27<00:00, 13.56s/it]\n"
|
| 468 |
+
]
|
| 469 |
+
},
|
| 470 |
+
{
|
| 471 |
+
"name": "stdout",
|
| 472 |
+
"output_type": "stream",
|
| 473 |
+
"text": [
|
| 474 |
+
"Note: tokenizer.model not found (this is OK for non-SentencePiece models)\n"
|
| 475 |
+
]
|
| 476 |
+
},
|
| 477 |
+
{
|
| 478 |
+
"name": "stderr",
|
| 479 |
+
"output_type": "stream",
|
| 480 |
+
"text": [
|
| 481 |
+
"Unsloth: Merging weights into 16bit: 100%|ββββββββββ| 2/2 [00:26<00:00, 13.47s/it]\n"
|
| 482 |
+
]
|
| 483 |
+
},
|
| 484 |
+
{
|
| 485 |
+
"name": "stdout",
|
| 486 |
+
"output_type": "stream",
|
| 487 |
+
"text": [
|
| 488 |
+
"Unsloth: Merge process complete. Saved to `/teamspace/studios/this_studio/Cashflowmanager/notebooks/outputs/cfo`\n",
|
| 489 |
+
"πΎ LoRA adapter saved β outputs/cfo\n"
|
| 490 |
+
]
|
| 491 |
+
}
|
| 492 |
+
],
|
| 493 |
+
"source": [
|
| 494 |
+
"from unsloth import FastLanguageModel\n",
|
| 495 |
+
"from unsloth.chat_templates import get_chat_template\n",
|
| 496 |
+
"from trl import SFTTrainer, SFTConfig\n",
|
| 497 |
+
"from transformers import TrainerCallback\n",
|
| 498 |
+
"class LossLogger(TrainerCallback):\n",
|
| 499 |
+
" def __init__(self):\n",
|
| 500 |
+
" self.losses, self.steps = [], []\n",
|
| 501 |
+
" def on_log(self, args, state, control, logs=None, **kwargs):\n",
|
| 502 |
+
" if logs and \"loss\" in logs:\n",
|
| 503 |
+
" self.losses.append(logs[\"loss\"])\n",
|
| 504 |
+
" self.steps.append(state.global_step)\n",
|
| 505 |
+
"# ββ Load model ββ\n",
|
| 506 |
+
"model, tokenizer = FastLanguageModel.from_pretrained(\n",
|
| 507 |
+
" model_name=MODEL_NAME,\n",
|
| 508 |
+
" max_seq_length=MAX_SEQ_LENGTH,\n",
|
| 509 |
+
" dtype=None,\n",
|
| 510 |
+
" load_in_4bit=True,\n",
|
| 511 |
+
")\n",
|
| 512 |
+
"tokenizer = get_chat_template(tokenizer, chat_template=CHAT_TEMPLATE)\n",
|
| 513 |
+
"if tokenizer.pad_token is None:\n",
|
| 514 |
+
" tokenizer.pad_token = tokenizer.eos_token\n",
|
| 515 |
+
"# ββ Attach LoRA ββ\n",
|
| 516 |
+
"model = FastLanguageModel.get_peft_model(\n",
|
| 517 |
+
" model,\n",
|
| 518 |
+
" r=LORA_R,\n",
|
| 519 |
+
" target_modules=[\"q_proj\", \"k_proj\", \"v_proj\", \"o_proj\",\n",
|
| 520 |
+
" \"gate_proj\", \"up_proj\", \"down_proj\"],\n",
|
| 521 |
+
" lora_alpha=LORA_ALPHA,\n",
|
| 522 |
+
" lora_dropout=0,\n",
|
| 523 |
+
" bias=\"none\",\n",
|
| 524 |
+
" use_gradient_checkpointing=\"unsloth\",\n",
|
| 525 |
+
" random_state=42,\n",
|
| 526 |
+
")\n",
|
| 527 |
+
"# ββ Build dataset ββ\n",
|
| 528 |
+
"with open(DATA_PATH) as f:\n",
|
| 529 |
+
" raw = [json.loads(l.strip()) for l in f if l.strip()]\n",
|
| 530 |
+
"print(f\"Loaded {len(raw)} CFO samples\")\n",
|
| 531 |
+
"dataset = Dataset.from_list(raw)\n",
|
| 532 |
+
"dataset = dataset.map(\n",
|
| 533 |
+
" lambda ex: {\"text\": [\n",
|
| 534 |
+
" tokenizer.apply_chat_template(m, tokenize=False, add_generation_prompt=False)\n",
|
| 535 |
+
" for m in ex[\"messages\"]\n",
|
| 536 |
+
" ]},\n",
|
| 537 |
+
" batched=True,\n",
|
| 538 |
+
" remove_columns=dataset.column_names,\n",
|
| 539 |
+
")\n",
|
| 540 |
+
"# ββ SFTConfig (filter to valid fields) ββ\n",
|
| 541 |
+
"valid_args = set(inspect.signature(SFTConfig.__init__).parameters.keys())\n",
|
| 542 |
+
"sft_kwargs = {k: v for k, v in {\n",
|
| 543 |
+
" \"output_dir\" : OUTPUT_DIR,\n",
|
| 544 |
+
" \"per_device_train_batch_size\" : BATCH_SIZE,\n",
|
| 545 |
+
" \"gradient_accumulation_steps\" : GRAD_ACCUM,\n",
|
| 546 |
+
" \"warmup_ratio\" : WARMUP_RATIO,\n",
|
| 547 |
+
" \"max_steps\" : MAX_STEPS,\n",
|
| 548 |
+
" \"learning_rate\" : LEARNING_RATE,\n",
|
| 549 |
+
" \"fp16\" : not torch.cuda.is_bf16_supported(),\n",
|
| 550 |
+
" \"bf16\" : torch.cuda.is_bf16_supported(),\n",
|
| 551 |
+
" \"logging_steps\" : 10,\n",
|
| 552 |
+
" \"save_steps\" : MAX_STEPS,\n",
|
| 553 |
+
" \"optim\" : \"adamw_8bit\",\n",
|
| 554 |
+
" \"weight_decay\" : WEIGHT_DECAY,\n",
|
| 555 |
+
" \"lr_scheduler_type\" : \"cosine\",\n",
|
| 556 |
+
" \"seed\" : 42,\n",
|
| 557 |
+
" \"report_to\" : \"none\",\n",
|
| 558 |
+
" \"dataset_text_field\" : \"text\",\n",
|
| 559 |
+
" \"max_seq_length\" : MAX_SEQ_LENGTH,\n",
|
| 560 |
+
" \"dataset_num_proc\" : 2,\n",
|
| 561 |
+
"}.items() if k in valid_args}\n",
|
| 562 |
+
"logger = LossLogger()\n",
|
| 563 |
+
"trainer = SFTTrainer(\n",
|
| 564 |
+
" model=model, tokenizer=tokenizer,\n",
|
| 565 |
+
" train_dataset=dataset,\n",
|
| 566 |
+
" args=SFTConfig(**sft_kwargs),\n",
|
| 567 |
+
" callbacks=[logger],\n",
|
| 568 |
+
")\n",
|
| 569 |
+
"print(f\"\\nTraining CFO agent on {MODEL_NAME} ...\")\n",
|
| 570 |
+
"stats = trainer.train()\n",
|
| 571 |
+
"print(f\"\\nβ
Final loss: {stats.training_loss:.4f}\")\n",
|
| 572 |
+
"# ββ Save LoRA adapter ββ\n",
|
| 573 |
+
"model.save_pretrained_merged(OUTPUT_DIR, tokenizer, save_method=\"lora\")\n",
|
| 574 |
+
"print(f\"πΎ LoRA adapter saved β {OUTPUT_DIR}\")"
|
| 575 |
+
]
|
| 576 |
+
},
|
| 577 |
+
{
|
| 578 |
+
"cell_type": "markdown",
|
| 579 |
+
"metadata": {},
|
| 580 |
+
"source": [
|
| 581 |
+
"## 5. Loss Curve"
|
| 582 |
+
]
|
| 583 |
+
},
|
| 584 |
+
{
|
| 585 |
+
"cell_type": "code",
|
| 586 |
+
"execution_count": 5,
|
| 587 |
+
"metadata": {},
|
| 588 |
+
"outputs": [
|
| 589 |
+
{
|
| 590 |
+
"data": {
|
| 591 |
+
"image/png": "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1113YcOGDRg5cqTcvvgCbwDg7e1tsC7915cuXap0X27evInc3FxkZmbi+PHjeO2113DvvfciOzsbgO67KiwsDF27dsVzzz0Hd3d3REVFYc6cOXj88cdx4cIFvPvuu+jVqxeefvppALohNYmJiWVu86677pKf63/nEhHVFI7xJiIyUUpKisFrNze3GtlO8R/bxdso6493Ly8vo8uU58cff4RWqwUAhIaGwt3dHe7u7ujRowcOHDiA3Nxc/Prrr3LCe+LECVy8eFFeftWqVfJYTBcXFzz44IOltrF69WpkZGQAANq0aSMn9nZ2dhg/fjzee+89ALo//J944olSy7du3Ro///wzJEnCwIEDsXLlSmRlZaGgoABXrlxB27Zt0atXL+zcuVNextPTUx6LWpGoqChERUWZ1NYYLy8vNG/evMrLl+eHH37AQw89BAC4//77AQAtW7bE0aNH8cUXX+Cff/5BcnJyqVtFFY9/rYzVq1eja9euAIC///4by5YtAwCcP3++UutRq9XYuHGjPEZ227Zt2LZtm7yu0NDQKn2OqsuKFSvk51OmTMFbb70FQPdjTWRkJI4ePQoAWLlyJbp162YwPrlZs2Zo1aqVPA5/6tSpBut2cnJCZmYm7OzscNddd6F9+/aws7MDoBuLfCcSEhLk5zXxXWNjY4OmTZvC3t6+1NW/L168iB9//BF9+vSR34/MzEx5vpWVlUF7/dfFx74p2x82bBgGDBiAxo0bIyMjAytWrMCmTZsAAAcPHsRnn32GV199FQDQtm1bHD58GCkpKUhJSYG/v798PYqnn34aQgh88803uHjxIp588kns378fQgg0btwYn3/+OQYNGmSwfVdXV/l5fHy8SX0mIroTTLyJiEzk7Oxs8Lq8akp5jF1ISP8Pa/0//BMTE6HVao0m3/qVQlMvZrRy5Ur5uX6le9y4cThw4AAAXaJSnHhfuHBBbmNra2vQ77IS3TNnzsjPT58+jXvuucdou9OnTxudft9998m3V1OpVHBxcUFWVhYAICkpqeydM9H333+P+fPnV3n5iRMnGiRz1UWj0RhNQOfMmYN33nmn3GX1z1IwhYODg5x0A4afv8q+x8HBwQYXpjK2rqp8jqrLf//9V+a2evXqJSfexe0ee+wxfPLJJ8jMzMRLL72El156Cfb29mjTpg1CQ0PxwgsvyEnblClTMGfO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",
|
| 592 |
+
"text/plain": [
|
| 593 |
+
"<Figure size 1000x400 with 1 Axes>"
|
| 594 |
+
]
|
| 595 |
+
},
|
| 596 |
+
"metadata": {},
|
| 597 |
+
"output_type": "display_data"
|
| 598 |
+
},
|
| 599 |
+
{
|
| 600 |
+
"name": "stdout",
|
| 601 |
+
"output_type": "stream",
|
| 602 |
+
"text": [
|
| 603 |
+
"π Saved β cfo_loss_curve.png\n"
|
| 604 |
+
]
|
| 605 |
+
}
|
| 606 |
+
],
|
| 607 |
+
"source": [
|
| 608 |
+
"fig, ax = plt.subplots(figsize=(10, 4))\n",
|
| 609 |
+
"ax.plot(logger.steps, logger.losses, color=\"#96CEB4\", linewidth=2, marker=\"o\", markersize=3)\n",
|
| 610 |
+
"ax.fill_between(logger.steps, logger.losses, alpha=0.15, color=\"#96CEB4\")\n",
|
| 611 |
+
"if logger.losses:\n",
|
| 612 |
+
" reduction = (1 - logger.losses[-1] / logger.losses[0]) * 100\n",
|
| 613 |
+
" ax.annotate(f\"Start: {logger.losses[0]:.3f}\", xy=(logger.steps[0], logger.losses[0]),\n",
|
| 614 |
+
" xytext=(5, 5), textcoords=\"offset points\", fontsize=9, color=\"red\")\n",
|
| 615 |
+
" ax.annotate(f\"End: {logger.losses[-1]:.3f}\", xy=(logger.steps[-1], logger.losses[-1]),\n",
|
| 616 |
+
" xytext=(-55, 5), textcoords=\"offset points\", fontsize=9, color=\"green\")\n",
|
| 617 |
+
" ax.set_title(f\"CFO Agent β Training Loss (β{reduction:.1f}%)\\n{MODEL_NAME}\",\n",
|
| 618 |
+
" fontsize=13, fontweight=\"bold\")\n",
|
| 619 |
+
"ax.set_xlabel(\"Step\")\n",
|
| 620 |
+
"ax.set_ylabel(\"Loss\")\n",
|
| 621 |
+
"ax.grid(True, alpha=0.3)\n",
|
| 622 |
+
"plt.tight_layout()\n",
|
| 623 |
+
"plt.savefig(\"cfo_loss_curve.png\", dpi=150)\n",
|
| 624 |
+
"plt.show()\n",
|
| 625 |
+
"print(\"π Saved β cfo_loss_curve.png\")\n"
|
| 626 |
+
]
|
| 627 |
+
},
|
| 628 |
+
{
|
| 629 |
+
"cell_type": "markdown",
|
| 630 |
+
"metadata": {},
|
| 631 |
+
"source": [
|
| 632 |
+
"## 6. Before vs After: CFO Response Comparison"
|
| 633 |
+
]
|
| 634 |
+
},
|
| 635 |
+
{
|
| 636 |
+
"cell_type": "code",
|
| 637 |
+
"execution_count": 6,
|
| 638 |
+
"metadata": {},
|
| 639 |
+
"outputs": [
|
| 640 |
+
{
|
| 641 |
+
"name": "stdout",
|
| 642 |
+
"output_type": "stream",
|
| 643 |
+
"text": [
|
| 644 |
+
"Loading model (single load for both inferences) ...\n",
|
| 645 |
+
"==((====))== Unsloth 2026.4.8: Fast Llama patching. Transformers: 5.5.0.\n",
|
| 646 |
+
" \\\\ /| Tesla T4. Num GPUs = 1. Max memory: 14.562 GB. Platform: Linux.\n",
|
| 647 |
+
"O^O/ \\_/ \\ Torch: 2.8.0+cu128. CUDA: 7.5. CUDA Toolkit: 12.8. Triton: 3.4.0\n",
|
| 648 |
+
"\\ / Bfloat16 = FALSE. FA [Xformers = None. FA2 = False]\n",
|
| 649 |
+
" \"-____-\" Free license: http://github.com/unslothai/unsloth\n",
|
| 650 |
+
"Unsloth: Fast downloading is enabled - ignore downloading bars which are red colored!\n"
|
| 651 |
+
]
|
| 652 |
+
},
|
| 653 |
+
{
|
| 654 |
+
"data": {
|
| 655 |
+
"application/vnd.jupyter.widget-view+json": {
|
| 656 |
+
"model_id": "6b2872cc696c4f9592111e509ad9539b",
|
| 657 |
+
"version_major": 2,
|
| 658 |
+
"version_minor": 0
|
| 659 |
+
},
|
| 660 |
+
"text/plain": [
|
| 661 |
+
"Loading weights: 0%| | 0/254 [00:00<?, ?it/s]"
|
| 662 |
+
]
|
| 663 |
+
},
|
| 664 |
+
"metadata": {},
|
| 665 |
+
"output_type": "display_data"
|
| 666 |
+
},
|
| 667 |
+
{
|
| 668 |
+
"name": "stderr",
|
| 669 |
+
"output_type": "stream",
|
| 670 |
+
"text": [
|
| 671 |
+
"Unsloth: Will load unsloth/Llama-3.2-3B-Instruct-bnb-4bit as a legacy tokenizer.\n",
|
| 672 |
+
"The attention mask is not set and cannot be inferred from input because pad token is same as eos token. As a consequence, you may observe unexpected behavior. Please pass your input's `attention_mask` to obtain reliable results.\n",
|
| 673 |
+
"Both `max_new_tokens` (=300) and `max_length`(=131072) seem to have been set. `max_new_tokens` will take precedence. Please refer to the documentation for more information. (https://huggingface.co/docs/transformers/main/en/main_classes/text_generation)\n"
|
| 674 |
+
]
|
| 675 |
+
},
|
| 676 |
+
{
|
| 677 |
+
"name": "stdout",
|
| 678 |
+
"output_type": "stream",
|
| 679 |
+
"text": [
|
| 680 |
+
"\n",
|
| 681 |
+
"Running base model inference ...\n"
|
| 682 |
+
]
|
| 683 |
+
},
|
| 684 |
+
{
|
| 685 |
+
"name": "stderr",
|
| 686 |
+
"output_type": "stream",
|
| 687 |
+
"text": [
|
| 688 |
+
"/home/zeus/miniconda3/envs/cloudspace/lib/python3.12/site-packages/transformers/modeling_attn_mask_utils.py:71: FutureWarning: The attention mask API under `transformers.modeling_attn_mask_utils` (`AttentionMaskConverter`) is deprecated and will be removed in Transformers v5.10. Please use the new API in `transformers.masking_utils`.\n",
|
| 689 |
+
" warnings.warn(DEPRECATION_MESSAGE, FutureWarning)\n",
|
| 690 |
+
"/home/zeus/miniconda3/envs/cloudspace/lib/python3.12/site-packages/transformers/modeling_attn_mask_utils.py:281: FutureWarning: The attention mask API under `transformers.modeling_attn_mask_utils` (`AttentionMaskConverter`) is deprecated and will be removed in Transformers v5.10. Please use the new API in `transformers.masking_utils`.\n",
|
| 691 |
+
" warnings.warn(DEPRECATION_MESSAGE, FutureWarning)\n",
|
| 692 |
+
"Both `max_new_tokens` (=300) and `max_length`(=131072) seem to have been set. `max_new_tokens` will take precedence. Please refer to the documentation for more information. (https://huggingface.co/docs/transformers/main/en/main_classes/text_generation)\n"
|
| 693 |
+
]
|
| 694 |
+
},
|
| 695 |
+
{
|
| 696 |
+
"name": "stdout",
|
| 697 |
+
"output_type": "stream",
|
| 698 |
+
"text": [
|
| 699 |
+
"Adapter attached. Running trained model inference ...\n",
|
| 700 |
+
"\n",
|
| 701 |
+
"============================================================\n",
|
| 702 |
+
"π΄ BASE MODEL (latency: 11.42s)\n",
|
| 703 |
+
"============================================================\n",
|
| 704 |
+
"```json\n",
|
| 705 |
+
"{\n",
|
| 706 |
+
" \"invoices\": [\n",
|
| 707 |
+
" {\n",
|
| 708 |
+
" \"invoice_id\": \"abc123\",\n",
|
| 709 |
+
" \"type\": \"pay\",\n",
|
| 710 |
+
" \"amount\": 120000,\n",
|
| 711 |
+
" \"reasoning\": \"Penalty cost βΉ8500\"\n",
|
| 712 |
+
" },\n",
|
| 713 |
+
" {\n",
|
| 714 |
+
" \"invoice_id\": \"def456\",\n",
|
| 715 |
+
" \"type\": \"defer\",\n",
|
| 716 |
+
" \"amount\": 300000,\n",
|
| 717 |
+
" \"reasoning\": \"\"\n",
|
| 718 |
+
" }\n",
|
| 719 |
+
" ],\n",
|
| 720 |
+
" \"cash\": {\n",
|
| 721 |
+
" \"current\": 150000,\n",
|
| 722 |
+
" \"available_credit\": 500000\n",
|
| 723 |
+
" }\n",
|
| 724 |
+
"}\n",
|
| 725 |
+
"```\n",
|
| 726 |
+
"\n",
|
| 727 |
+
"Explanation:\n",
|
| 728 |
+
"\n",
|
| 729 |
+
"* We have two invoices to process today (`abc123` and `def456`). \n",
|
| 730 |
+
"* For `abc123`, we need to pay the full amount as there is no discount mentioned in the advisors. The reason for this payment is that it comes with a penalty cost of βΉ8500.\n",
|
| 731 |
+
"* For `def456`, since its due date is far away (5 days from now) and our current deficit is relatively low compared to other options available, we can defer paying this amount until later. This decision was made based on the advisor's recommendation to prioritize immediate payments over future ones when possible.\n",
|
| 732 |
+
"* Our current cash balance is βΉ150,000, which is lower than our available credit limit of βΉ500,000. Therefore, we should draw upon our available credit to cover any shortfalls or unexpected expenses. \n",
|
| 733 |
+
"\n",
|
| 734 |
+
"This output provides an overview of both the invoices being processed and the current state of our\n",
|
| 735 |
+
"\n",
|
| 736 |
+
"============================================================\n",
|
| 737 |
+
"π’ TRAINED CFO (latency: 2.03s)\n",
|
| 738 |
+
"============================================================\n",
|
| 739 |
+
"{\n",
|
| 740 |
+
" \"invoice_id\": null,\n",
|
| 741 |
+
" \"type\": \"defer\",\n",
|
| 742 |
+
" \"amount\": 0,\n",
|
| 743 |
+
" \"reasoning\": \"Deficit of βΉ50000 expected.\"\n",
|
| 744 |
+
"}\n",
|
| 745 |
+
"\n",
|
| 746 |
+
"ββ JSON validity ββ\n",
|
| 747 |
+
" Base : β not valid JSON (Extra data: line 21 column 1 (char 414))\n",
|
| 748 |
+
" Trained: β
valid JSON β keys: ['invoice_id', 'type', 'amount', 'reasoning']\n"
|
| 749 |
+
]
|
| 750 |
+
}
|
| 751 |
+
],
|
| 752 |
+
"source": [
|
| 753 |
+
"import os, re, time, gc\n",
|
| 754 |
+
"from peft import PeftModel\n",
|
| 755 |
+
"\n",
|
| 756 |
+
"os.environ[\"TORCH_COMPILE_DISABLE\"] = \"1\"\n",
|
| 757 |
+
"OUTPUT_DIR = \"/teamspace/studios/this_studio/Cashflowmanager/notebooks/outputs/cfo/checkpoint-50\"\n",
|
| 758 |
+
"\n",
|
| 759 |
+
"CFO_SYSTEM = (\n",
|
| 760 |
+
" \"You are the CFO of a company managing daily cash flow decisions. \"\n",
|
| 761 |
+
" \"Output valid JSON with keys: invoice_id, type (pay|defer|negotiate|credit), \"\n",
|
| 762 |
+
" \"amount, reasoning.\"\n",
|
| 763 |
+
")\n",
|
| 764 |
+
"TEST_PROMPT = (\n",
|
| 765 |
+
" \"Day: 3 | Cash: βΉ150000 | Credit: βΉ0/500000\\n\\n\"\n",
|
| 766 |
+
" \"ADVISORS:\\n\"\n",
|
| 767 |
+
" \"[Expenditure]: [PAY_IMMEDIATELY] Invoice abc123 has penalty cost βΉ8500. Pay immediately.\\n\"\n",
|
| 768 |
+
" \"[Revenue]: [CASH_TIGHT] Deficit of βΉ50000 expected. Draw credit immediately.\\n\"\n",
|
| 769 |
+
" \"[Risk]: [HIGH] Credit: 0%. 2 threats detected. Build cash reserves.\\n\\n\"\n",
|
| 770 |
+
" \"INVOICES:\\n- abc123: βΉ120000 due 1d\\n- def456: βΉ300000 due 5d\"\n",
|
| 771 |
+
")\n",
|
| 772 |
+
"\n",
|
| 773 |
+
"\n",
|
| 774 |
+
"def run_inference(mdl, tok, max_new_tokens=300):\n",
|
| 775 |
+
" msgs = [{\"role\": \"system\", \"content\": CFO_SYSTEM},\n",
|
| 776 |
+
" {\"role\": \"user\", \"content\": TEST_PROMPT}]\n",
|
| 777 |
+
" inp = tok.apply_chat_template(\n",
|
| 778 |
+
" msgs, add_generation_prompt=True, return_tensors=\"pt\"\n",
|
| 779 |
+
" ).to(\"cuda\")\n",
|
| 780 |
+
" t0 = time.time()\n",
|
| 781 |
+
" with torch.no_grad():\n",
|
| 782 |
+
" out = mdl.generate(\n",
|
| 783 |
+
" input_ids=inp, max_new_tokens=max_new_tokens,\n",
|
| 784 |
+
" do_sample=False, repetition_penalty=1.15, use_cache=True\n",
|
| 785 |
+
" )\n",
|
| 786 |
+
" return tok.decode(out[0][inp.shape[1]:], skip_special_tokens=True).strip(), time.time() - t0\n",
|
| 787 |
+
"\n",
|
| 788 |
+
"\n",
|
| 789 |
+
"def check_json(text, label):\n",
|
| 790 |
+
" try:\n",
|
| 791 |
+
" parsed = json.loads(re.sub(r\"^[^{\\[]*\", \"\", text))\n",
|
| 792 |
+
" keys = list(parsed.keys()) if isinstance(parsed, dict) else type(parsed)\n",
|
| 793 |
+
" print(f\" {label}: β
valid JSON β keys: {keys}\")\n",
|
| 794 |
+
" except Exception as e:\n",
|
| 795 |
+
" print(f\" {label}: β not valid JSON ({e})\")\n",
|
| 796 |
+
"\n",
|
| 797 |
+
"\n",
|
| 798 |
+
"# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ\n",
|
| 799 |
+
"# Load the model ONCE β run base inference first, then attach adapter\n",
|
| 800 |
+
"# This avoids the double-load OOM that caused the ValueError\n",
|
| 801 |
+
"# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ\n",
|
| 802 |
+
"print(\"Loading model (single load for both inferences) ...\")\n",
|
| 803 |
+
"model, tokenizer = FastLanguageModel.from_pretrained(\n",
|
| 804 |
+
" model_name=MODEL_NAME,\n",
|
| 805 |
+
" max_seq_length=MAX_SEQ_LENGTH,\n",
|
| 806 |
+
" dtype=None,\n",
|
| 807 |
+
" load_in_4bit=True,\n",
|
| 808 |
+
")\n",
|
| 809 |
+
"tokenizer = get_chat_template(tokenizer, chat_template=CHAT_TEMPLATE)\n",
|
| 810 |
+
"if tokenizer.pad_token is None:\n",
|
| 811 |
+
" tokenizer.pad_token = tokenizer.eos_token\n",
|
| 812 |
+
"\n",
|
| 813 |
+
"# ββ 1. Base inference (no adapter) βββββββββββββββββββββββββββββββββββββββββββ\n",
|
| 814 |
+
"FastLanguageModel.for_inference(model)\n",
|
| 815 |
+
"print(\"\\nRunning base model inference ...\")\n",
|
| 816 |
+
"base_resp, base_lat = run_inference(model, tokenizer)\n",
|
| 817 |
+
"\n",
|
| 818 |
+
"# ββ 2. Attach LoRA adapter in-place, re-run inference ββββββββββββββββββββββββ\n",
|
| 819 |
+
"# Disable cache before wrapping with PeftModel\n",
|
| 820 |
+
"model.config.use_cache = False\n",
|
| 821 |
+
"trained_model = PeftModel.from_pretrained(model, OUTPUT_DIR, is_trainable=False)\n",
|
| 822 |
+
"FastLanguageModel.for_inference(trained_model)\n",
|
| 823 |
+
"print(\"Adapter attached. Running trained model inference ...\")\n",
|
| 824 |
+
"train_resp, train_lat = run_inference(trained_model, tokenizer)\n",
|
| 825 |
+
"\n",
|
| 826 |
+
"# ββ Cleanup βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ\n",
|
| 827 |
+
"del trained_model, model\n",
|
| 828 |
+
"torch.cuda.empty_cache(); gc.collect()\n",
|
| 829 |
+
"\n",
|
| 830 |
+
"# ββ Results βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ\n",
|
| 831 |
+
"print(f'\\n{\"=\"*60}\\nπ΄ BASE MODEL (latency: {base_lat:.2f}s)\\n{\"=\"*60}')\n",
|
| 832 |
+
"print(base_resp)\n",
|
| 833 |
+
"print(f'\\n{\"=\"*60}\\nπ’ TRAINED CFO (latency: {train_lat:.2f}s)\\n{\"=\"*60}')\n",
|
| 834 |
+
"print(train_resp)\n",
|
| 835 |
+
"\n",
|
| 836 |
+
"print(\"\\nββ JSON validity ββ\")\n",
|
| 837 |
+
"check_json(base_resp, \"Base \")\n",
|
| 838 |
+
"check_json(train_resp, \"Trained\")"
|
| 839 |
+
]
|
| 840 |
+
},
|
| 841 |
+
{
|
| 842 |
+
"cell_type": "code",
|
| 843 |
+
"execution_count": null,
|
| 844 |
+
"id": "cd98a407",
|
| 845 |
+
"metadata": {},
|
| 846 |
+
"outputs": [],
|
| 847 |
+
"source": []
|
| 848 |
+
}
|
| 849 |
+
],
|
| 850 |
+
"metadata": {
|
| 851 |
+
"kernelspec": {
|
| 852 |
+
"display_name": "Python 3",
|
| 853 |
+
"language": "python",
|
| 854 |
+
"name": "python3"
|
| 855 |
+
},
|
| 856 |
+
"language_info": {
|
| 857 |
+
"name": "python",
|
| 858 |
+
"version": "3.10.0"
|
| 859 |
+
}
|
| 860 |
+
},
|
| 861 |
+
"nbformat": 4,
|
| 862 |
+
"nbformat_minor": 5
|
| 863 |
+
}
|
notebooks/RL_Loss_curve.png
ADDED
|
notebooks/SFT_CFO_Loss.png
ADDED
|