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