asdf98 commited on
Commit
5279226
·
verified ·
1 Parent(s): 6b71901

Root-fix Qwen3 Unsloth notebook with clean install and current SFTConfig

Browse files
EthicalHacking_Qwen3-4B_Ultimate_Colab.ipynb CHANGED
@@ -1,548 +1,26 @@
1
  {
2
  "cells": [
3
- {
4
- "cell_type": "markdown",
5
- "metadata": {},
6
- "source": [
7
- "# \ud83d\udd10 Ultimate LLM Fine-Tuning \u2013 Qwen3-4B (Colab Free Tier T4)\n",
8
- "\n",
9
- "**\ud83e\udd47 Model:** [Qwen3-4B-Instruct-2507](https://huggingface.co/Qwen/Qwen3-4B-Instruct-2507) via Unsloth 4-bit \n",
10
- "**\ud83c\udfc6 Why this model?** Highest coding/reasoning scores among sub-10B models (LiveCodeBench 35.1, MMLU-Pro 69.6). Only **3.3 GB** in 4-bit. \n",
11
- "**\ud83d\udcca Datasets:** Your choice \u2014 cybersecurity, general chat, multilingual, coding, or mix them! \n",
12
- "**\u26a1 Framework:** Unsloth + TRL SFTTrainer \u2014 2\u00d7 faster, 70% less VRAM \n",
13
- "\n",
14
- "> \u26a0\ufe0f Default is cybersecurity. Pick general-purpose datasets for other domains.\n",
15
- "\n",
16
- "---\n",
17
- "\n",
18
- "## \ud83d\udccb T4 VRAM Cheat-Sheet\n",
19
- "\n",
20
- "| Setting | Value | Why |\n",
21
- "|---------|-------|-----|\n",
22
- "| `MAX_SEQ_LENGTH` | 4096 | Huge headroom on T4 |\n",
23
- "| `LORA_R` | 64 | Higher rank = more capacity |\n",
24
- "| `BATCH_SIZE` | 4 | You have ~11GB free VRAM |\n",
25
- "| `GRAD_ACCUM` | 2 | Effective batch = 8 |\n",
26
- "| `PACKING` | **False** | Avoids Unsloth `num_items_in_batch` bug (stable) |\n",
27
- "| `optim` | `adamw_8bit` | Massive VRAM saver |\n",
28
- "\n",
29
- "If you still hit OOM \u2192 lower `MAX_SEQ_LENGTH` to 3072 or set `use_rslora=True`."
30
- ]
31
- },
32
- {
33
- "cell_type": "markdown",
34
- "metadata": {},
35
- "source": [
36
- "## 1\ufe0f\u20e3 Install Dependencies"
37
- ]
38
- },
39
- {
40
- "cell_type": "code",
41
- "execution_count": null,
42
- "metadata": {},
43
- "outputs": [],
44
- "source": [
45
- "%%capture\n",
46
- "!pip install -q unsloth trl datasets accelerate transformers bitsandbytes huggingface_hub"
47
- ]
48
- },
49
- {
50
- "cell_type": "markdown",
51
- "metadata": {},
52
- "source": [
53
- "## 2\ufe0f\u20e3 (Optional) Login to HuggingFace Hub"
54
- ]
55
- },
56
- {
57
- "cell_type": "code",
58
- "execution_count": null,
59
- "metadata": {},
60
- "outputs": [],
61
- "source": [
62
- "from huggingface_hub import login\n",
63
- "# login(token=\"hf_YOUR_TOKEN\") # \u2190 uncomment and paste your token"
64
- ]
65
- },
66
- {
67
- "cell_type": "markdown",
68
- "metadata": {},
69
- "source": [
70
- "## 3\ufe0f\u20e3 Load Qwen3-4B-Instruct-2507 in 4-bit via Unsloth\n",
71
- "\n",
72
- "**\u26a0\ufe0f IMPORTANT:** We add `device_map={\"\": torch.cuda.current_device()}` to force the model onto the correct GPU.\n",
73
- "Without this, `accelerate` may place the model on CPU and throw a `ValueError` during training on Kaggle/Colab."
74
- ]
75
- },
76
- {
77
- "cell_type": "code",
78
- "execution_count": null,
79
- "metadata": {},
80
- "outputs": [],
81
- "source": [
82
- "from unsloth import FastLanguageModel\n",
83
- "import torch\n",
84
- "\n",
85
- "# ==================== T4-COLAB HYPERPARAMETERS ====================\n",
86
- "MAX_SEQ_LENGTH = 4096\n",
87
- "LORA_R = 64\n",
88
- "LORA_ALPHA = 64\n",
89
- "BATCH_SIZE = 4\n",
90
- "GRAD_ACCUM = 2\n",
91
- "LEARNING_RATE = 2e-4\n",
92
- "MAX_STEPS = 4000\n",
93
- "WARMUP_STEPS = 200\n",
94
- "LOGGING_STEPS = 50\n",
95
- "SAVE_STEPS = 500\n",
96
- "PACKING = False # \u2190 MUST be False to avoid Unsloth num_items_in_batch bug\n",
97
- "SAMPLE_SIZE = 50000\n",
98
- "HUB_MODEL_ID = \"your-username/cyber-qwen3-4b-lora\"\n",
99
- "# ==================================================================\n",
100
- "\n",
101
- "model, tokenizer = FastLanguageModel.from_pretrained(\n",
102
- " model_name=\"unsloth/Qwen3-4B-Instruct-2507-unsloth-bnb-4bit\",\n",
103
- " max_seq_length=MAX_SEQ_LENGTH,\n",
104
- " dtype=None,\n",
105
- " load_in_4bit=True,\n",
106
- " device_map={\"\": torch.cuda.current_device()}, # \u2190 FORCE GPU: fixes Kaggle/Colab device placement bug\n",
107
- ")\n",
108
- "\n",
109
- "model = FastLanguageModel.get_peft_model(\n",
110
- " model,\n",
111
- " r=LORA_R,\n",
112
- " target_modules=[\"q_proj\", \"k_proj\", \"v_proj\", \"o_proj\",\n",
113
- " \"gate_proj\", \"up_proj\", \"down_proj\"],\n",
114
- " lora_alpha=LORA_ALPHA,\n",
115
- " lora_dropout=0,\n",
116
- " bias=\"none\",\n",
117
- " use_gradient_checkpointing=\"unsloth\",\n",
118
- " random_state=3407,\n",
119
- " use_rslora=False,\n",
120
- " loftq_config=None,\n",
121
- ")\n",
122
- "\n",
123
- "trainable = sum(p.numel() for p in model.parameters() if p.requires_grad)\n",
124
- "total = sum(p.numel() for p in model.parameters())\n",
125
- "print(f\"\u2705 Qwen3-4B loaded. Trainable params: {trainable:,} / {total:,} ({100*trainable/total:.2f}%)\")"
126
- ]
127
- },
128
- {
129
- "cell_type": "markdown",
130
- "metadata": {},
131
- "source": [
132
- "## 4\ufe0f\u20e3 \ud83c\udfaf CHOOSE YOUR DATASET(S)\n",
133
- "\n",
134
- "Uncomment **ONE** `DATASET_CHOICE` line to select your training data.\n",
135
- "\n",
136
- "| Choice | Dataset | Rows | Format | Best For |\n",
137
- "|--------|---------|------|--------|----------|\n",
138
- "| `\"cybersecurity\"` | Fenrir v2.1 + Trendyol | 153K\u219250K | system/user/assistant | Ethical hacking education |\n",
139
- "| `\"ultrachat\"` | UltraChat 200K SFT | 200K\u219250K | messages (user/assistant) | General conversation |\n",
140
- "| `\"openhermes\"` | OpenHermes 2.5 | 1M+\u219250K | conversations (human/gpt) | Reasoning, coding |\n",
141
- "| `\"sharegpt_en\"` | ShareGPT English | ~90K\u219250K | conversations (human/gpt) | Multi-turn dialogue |\n",
142
- "| `\"sharegpt_de\"` | ShareGPT German | ~104K\u219250K | conversations (human/gpt) | German fine-tuning |\n",
143
- "| `\"sharegpt_hi\"` | ShareGPT Hindi | ~153K\u219250K | conversations (human/gpt) | Hindi fine-tuning |\n",
144
- "| `\"code_corpus\"` | [Code Corpus LLM Training](https://huggingface.co/datasets/krystv/code-corpus-llm-training) | 240K\u219250K | text (code files) | **Code completion, coding assistant** |\n",
145
- "| `\"custom_mix\"` | Mix of your choice | \u2014 | varies | Combine datasets |"
146
- ]
147
- },
148
- {
149
- "cell_type": "code",
150
- "execution_count": null,
151
- "metadata": {},
152
- "outputs": [],
153
- "source": [
154
- "from datasets import load_dataset, concatenate_datasets\n",
155
- "\n",
156
- "# \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n",
157
- "# SELECT YOUR DATASET \u2014 UNCOMMENT ONE LINE\n",
158
- "# \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n",
159
- "\n",
160
- "DATASET_CHOICE = \"cybersecurity\"\n",
161
- "\n",
162
- "# DATASET_CHOICE = \"ultrachat\"\n",
163
- "# DATASET_CHOICE = \"openhermes\"\n",
164
- "# DATASET_CHOICE = \"sharegpt_en\"\n",
165
- "# DATASET_CHOICE = \"sharegpt_de\"\n",
166
- "# DATASET_CHOICE = \"sharegpt_hi\"\n",
167
- "# DATASET_CHOICE = \"code_corpus\"\n",
168
- "# DATASET_CHOICE = \"custom_mix\"\n",
169
- "\n",
170
- "CUSTOM_DATASETS = [\n",
171
- " # (\"dataset_name_or_id\", \"split\", rows_to_take, \"format_type\")\n",
172
- " # format_type: \"messages\" | \"conversations\" | \"text\"\n",
173
- " (\"AlicanKiraz0/Cybersecurity-Dataset-Fenrir-v2.1\", \"train\", 10000, \"messages\"),\n",
174
- " (\"HuggingFaceH4/ultrachat_200k\", \"train_sft\", 20000, \"messages\"),\n",
175
- " (\"teknium/OpenHermes-2.5\", \"train\", 20000, \"conversations\"),\n",
176
- "]\n",
177
- "\n",
178
- "print(f\"\ud83c\udfaf DATASET_CHOICE = {DATASET_CHOICE}\")"
179
- ]
180
- },
181
- {
182
- "cell_type": "markdown",
183
- "metadata": {},
184
- "source": [
185
- "## 5\ufe0f\u20e3 Load, Convert & Pre-process Selected Dataset\n",
186
- "\n",
187
- "Auto-detects dataset format and converts everything to standard `messages` \u2192 `text`."
188
- ]
189
- },
190
- {
191
- "cell_type": "code",
192
- "execution_count": null,
193
- "metadata": {},
194
- "outputs": [],
195
- "source": [
196
- "import random\n",
197
- "\n",
198
- "def _convert_fenrir(example):\n",
199
- " return {\"messages\": [\n",
200
- " {\"role\": \"system\", \"content\": example[\"system\"]},\n",
201
- " {\"role\": \"user\", \"content\": example[\"user\"]},\n",
202
- " {\"role\": \"assistant\", \"content\": example[\"assistant\"]},\n",
203
- " ]}\n",
204
- "\n",
205
- "def _convert_trendyol(example):\n",
206
- " return {\"messages\": [\n",
207
- " {\"role\": \"system\", \"content\": example[\"system\"]},\n",
208
- " {\"role\": \"user\", \"content\": example[\"user\"]},\n",
209
- " {\"role\": \"assistant\", \"content\": example[\"assistant\"]},\n",
210
- " ]}\n",
211
- "\n",
212
- "def _convert_ultrachat(example):\n",
213
- " return {\"messages\": example[\"messages\"]}\n",
214
- "\n",
215
- "def _convert_conversations(example):\n",
216
- " msgs = []\n",
217
- " system_prompt = example.get(\"system_prompt\", \"\") or example.get(\"system\", \"\")\n",
218
- " if system_prompt:\n",
219
- " msgs.append({\"role\": \"system\", \"content\": system_prompt})\n",
220
- " for turn in example[\"conversations\"]:\n",
221
- " role = \"user\" if turn[\"from\"] in (\"human\", \"user\") else \"assistant\"\n",
222
- " msgs.append({\"role\": role, \"content\": turn[\"value\"]})\n",
223
- " return {\"messages\": msgs}\n",
224
- "\n",
225
- "def _convert_code_corpus(example):\n",
226
- " code_text = example[\"text\"]\n",
227
- " domain = example.get(\"domain\", \"code\")\n",
228
- " repo = example.get(\"repo\", \"unknown\")\n",
229
- " lang = example.get(\"language\", \"\")\n",
230
- " user_prompt = f\"Here is a code snippet from the {domain} domain (repo: {repo}, language: {lang}). Please explain or improve it.\"\n",
231
- " return {\"messages\": [\n",
232
- " {\"role\": \"user\", \"content\": user_prompt},\n",
233
- " {\"role\": \"assistant\", \"content\": code_text},\n",
234
- " ]}\n",
235
- "\n",
236
- "all_datasets = []\n",
237
- "\n",
238
- "if DATASET_CHOICE == \"cybersecurity\":\n",
239
- " ds1 = load_dataset(\"AlicanKiraz0/Cybersecurity-Dataset-Fenrir-v2.1\", split=\"train\")\n",
240
- " ds1 = ds1.map(_convert_fenrir, remove_columns=ds1.column_names, batched=False)\n",
241
- " all_datasets.append(ds1)\n",
242
- " ds2 = load_dataset(\"Trendyol/Trendyol-Cybersecurity-Instruction-Tuning-Dataset\", split=\"train\")\n",
243
- " ds2 = ds2.map(_convert_trendyol, remove_columns=ds2.column_names, batched=False)\n",
244
- " all_datasets.append(ds2)\n",
245
- "\n",
246
- "elif DATASET_CHOICE == \"ultrachat\":\n",
247
- " ds = load_dataset(\"HuggingFaceH4/ultrachat_200k\", split=\"train_sft\")\n",
248
- " ds = ds.map(_convert_ultrachat, remove_columns=ds.column_names, batched=False)\n",
249
- " all_datasets.append(ds)\n",
250
- "\n",
251
- "elif DATASET_CHOICE == \"openhermes\":\n",
252
- " ds = load_dataset(\"teknium/OpenHermes-2.5\", split=\"train\")\n",
253
- " ds = ds.map(_convert_conversations, remove_columns=ds.column_names, batched=False)\n",
254
- " all_datasets.append(ds)\n",
255
- "\n",
256
- "elif DATASET_CHOICE.startswith(\"sharegpt_\"):\n",
257
- " split_map = {\"sharegpt_en\": \"english\", \"sharegpt_de\": \"german_4b_translated\", \"sharegpt_hi\": \"hindi_27b_translated\"}\n",
258
- " ds = load_dataset(\"deepmage121/ShareGPT_multilingual\", split=split_map[DATASET_CHOICE])\n",
259
- " ds = ds.map(_convert_conversations, remove_columns=ds.column_names, batched=False)\n",
260
- " all_datasets.append(ds)\n",
261
- "\n",
262
- "elif DATASET_CHOICE == \"code_corpus\":\n",
263
- " print(\"\ud83d\udce5 Loading Code Corpus LLM Training (krystv)...\")\n",
264
- " ds = load_dataset(\"krystv/code-corpus-llm-training\", split=\"train\")\n",
265
- " ds = ds.map(_convert_code_corpus, remove_columns=ds.column_names, batched=False)\n",
266
- " all_datasets.append(ds)\n",
267
- "\n",
268
- "elif DATASET_CHOICE == \"custom_mix\":\n",
269
- " for ds_id, split, n_rows, fmt in CUSTOM_DATASETS:\n",
270
- " print(f\"\ud83d\udce5 Loading {ds_id} ({split}, {n_rows} rows)...\")\n",
271
- " ds = load_dataset(ds_id, split=split)\n",
272
- " if n_rows and len(ds) > n_rows:\n",
273
- " ds = ds.shuffle(seed=3407).select(range(n_rows))\n",
274
- " if fmt == \"messages\":\n",
275
- " ds = ds.map(_convert_ultrachat, remove_columns=ds.column_names, batched=False)\n",
276
- " elif fmt == \"conversations\":\n",
277
- " ds = ds.map(_convert_conversations, remove_columns=ds.column_names, batched=False)\n",
278
- " elif fmt == \"text\":\n",
279
- " ds = ds.map(_convert_code_corpus, remove_columns=ds.column_names, batched=False)\n",
280
- " else:\n",
281
- " raise ValueError(f\"Unknown format: {fmt}\")\n",
282
- " all_datasets.append(ds)\n",
283
- "\n",
284
- "else:\n",
285
- " raise ValueError(f\"Unknown DATASET_CHOICE: {DATASET_CHOICE}\")\n",
286
- "\n",
287
- "train_dataset = concatenate_datasets(all_datasets) if len(all_datasets) > 1 else all_datasets[0]\n",
288
- "print(f\"\\n\ud83d\udcca COMBINED DATASET: {len(train_dataset)} rows\")\n",
289
- "\n",
290
- "sample = train_dataset[random.randint(0, len(train_dataset)-1)]\n",
291
- "print(f\"Sample roles: {[m['role'] for m in sample['messages']]}\")\n",
292
- "for m in sample[\"messages\"]: print(f\" {m['role']}: {m['content'][:80]}...\")\n",
293
- "\n",
294
- "if len(train_dataset) > SAMPLE_SIZE:\n",
295
- " train_dataset = train_dataset.shuffle(seed=3407).select(range(SAMPLE_SIZE))\n",
296
- " print(f\"\\n\ud83d\ude80 SUBSAMPLED to {len(train_dataset)} rows\")\n",
297
- "\n",
298
- "print(f\" Effective batch size: {BATCH_SIZE * GRAD_ACCUM}\")\n",
299
- "print(f\" Steps per epoch: ~{len(train_dataset) // (BATCH_SIZE * GRAD_ACCUM)}\")\n",
300
- "print(f\" Capped to MAX_STEPS: {MAX_STEPS}\")"
301
- ]
302
- },
303
- {
304
- "cell_type": "markdown",
305
- "metadata": {},
306
- "source": [
307
- "## 6\ufe0f\u20e3 Convert Messages \u2192 Text (Chat Template)"
308
- ]
309
- },
310
- {
311
- "cell_type": "code",
312
- "execution_count": null,
313
- "metadata": {},
314
- "outputs": [],
315
- "source": [
316
- "def convert_messages_to_text(examples):\n",
317
- " texts = []\n",
318
- " for msgs in examples[\"messages\"]:\n",
319
- " text = tokenizer.apply_chat_template(msgs, tokenize=False, add_generation_prompt=False)\n",
320
- " texts.append(text)\n",
321
- " return {\"text\": texts}\n",
322
- "\n",
323
- "print(\"\ud83d\udd04 Converting messages to text...\")\n",
324
- "train_dataset = train_dataset.map(convert_messages_to_text, batched=True, remove_columns=[\"messages\"], batch_size=100)\n",
325
- "print(f\"\u2705 Dataset pre-processed. Columns: {train_dataset.column_names}\")\n",
326
- "print(f\"\ud83d\udcc4 Sample text length: {len(train_dataset[0]['text'])} chars\")"
327
- ]
328
- },
329
- {
330
- "cell_type": "markdown",
331
- "metadata": {},
332
- "source": [
333
- "## 7\ufe0f\u20e3 Configure SFT Trainer"
334
- ]
335
- },
336
- {
337
- "cell_type": "code",
338
- "execution_count": null,
339
- "metadata": {},
340
- "outputs": [],
341
- "source": [
342
- "from trl import SFTTrainer\n",
343
- "from transformers import TrainingArguments\n",
344
- "\n",
345
- "trainer = SFTTrainer(\n",
346
- " model=model,\n",
347
- " tokenizer=tokenizer,\n",
348
- " train_dataset=train_dataset,\n",
349
- " dataset_text_field=\"text\",\n",
350
- " max_seq_length=MAX_SEQ_LENGTH,\n",
351
- " dataset_num_proc=2,\n",
352
- " packing=PACKING,\n",
353
- " args=TrainingArguments(\n",
354
- " per_device_train_batch_size=BATCH_SIZE,\n",
355
- " gradient_accumulation_steps=GRAD_ACCUM,\n",
356
- " warmup_steps=WARMUP_STEPS,\n",
357
- " max_steps=MAX_STEPS,\n",
358
- " learning_rate=LEARNING_RATE,\n",
359
- " fp16=True,\n",
360
- " logging_steps=LOGGING_STEPS,\n",
361
- " optim=\"adamw_8bit\",\n",
362
- " weight_decay=0.01,\n",
363
- " lr_scheduler_type=\"linear\",\n",
364
- " seed=3407,\n",
365
- " output_dir=\"./outputs\",\n",
366
- " save_strategy=\"steps\",\n",
367
- " save_steps=SAVE_STEPS,\n",
368
- " save_total_limit=2,\n",
369
- " report_to=\"none\",\n",
370
- " ),\n",
371
- ")\n",
372
- "\n",
373
- "print(f\"\u2705 Trainer ready. Dataset: {DATASET_CHOICE} | Steps: {MAX_STEPS}\")\n",
374
- "print(f\" Effective batch size: {BATCH_SIZE * GRAD_ACCUM}\")\n",
375
- "print(f\" Packing enabled: {PACKING}\")"
376
- ]
377
- },
378
- {
379
- "cell_type": "markdown",
380
- "metadata": {},
381
- "source": [
382
- "## 8\ufe0f\u20e3 Train \ud83d\ude80"
383
- ]
384
- },
385
- {
386
- "cell_type": "code",
387
- "execution_count": null,
388
- "metadata": {},
389
- "outputs": [],
390
- "source": [
391
- "# \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n",
392
- "# RUNTIME PATCH: Fix num_items_in_batch int bug\n",
393
- "# Must run AFTER trainer = SFTTrainer(...) \n",
394
- "# and BEFORE trainer.train()\n",
395
- "# \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n",
396
- "import types\n",
397
- "\n",
398
- "# Walk the trainer's class hierarchy and patch every training_step\n",
399
- "trainer_cls = type(trainer)\n",
400
- "\n",
401
- "for cls in trainer_cls.__mro__:\n",
402
- " if 'training_step' in cls.__dict__:\n",
403
- " _orig_step = cls.__dict__['training_step']\n",
404
- " \n",
405
- " def _make_safe_step(original):\n",
406
- " def _safe_training_step(self, model, inputs, num_items_in_batch=None, **kwargs):\n",
407
- " if isinstance(num_items_in_batch, int):\n",
408
- " num_items_in_batch = None\n",
409
- " return original(self, model, inputs, num_items_in_batch=num_items_in_batch, **kwargs)\n",
410
- " return _safe_training_step\n",
411
- " \n",
412
- " cls.training_step = _make_safe_step(_orig_step)\n",
413
- " print(f\"Patched {cls.__name__}.training_step\")\n",
414
- "\n",
415
- "print(\"Ready to run trainer.train()\")"
416
- ]
417
- },
418
- {
419
- "cell_type": "code",
420
- "execution_count": null,
421
- "metadata": {},
422
- "outputs": [],
423
- "source": [
424
- "if torch.cuda.is_available():\n",
425
- " print(f\"VRAM before train: {torch.cuda.memory_allocated()/1e9:.2f} GB / {torch.cuda.get_device_properties(0).total_memory/1e9:.2f} GB\")\n",
426
- "\n",
427
- "trainer_stats = trainer.train()\n",
428
- "\n",
429
- "print(\"\\n\ud83c\udf89 Training complete!\")\n",
430
- "print(trainer_stats)\n",
431
- "\n",
432
- "if torch.cuda.is_available():\n",
433
- " print(f\"VRAM after train: {torch.cuda.memory_allocated()/1e9:.2f} GB\")"
434
- ]
435
- },
436
- {
437
- "cell_type": "markdown",
438
- "metadata": {},
439
- "source": [
440
- "## 9\ufe0f\u20e3 Save & Push to HuggingFace Hub"
441
- ]
442
- },
443
- {
444
- "cell_type": "code",
445
- "execution_count": null,
446
- "metadata": {},
447
- "outputs": [],
448
- "source": [
449
- "model.save_pretrained(\"./lora-adapter\")\n",
450
- "tokenizer.save_pretrained(\"./lora-adapter\")\n",
451
- "print(\"\u2705 LoRA adapter saved\")\n",
452
- "\n",
453
- "print(\"\\n\ud83d\udd04 Merging LoRA into base model...\")\n",
454
- "merged_model = model.merge_and_unload()\n",
455
- "merged_model.save_pretrained(\"./merged-model\")\n",
456
- "tokenizer.save_pretrained(\"./merged-model\")\n",
457
- "print(\"\u2705 Merged model saved\")\n",
458
- "\n",
459
- "# model.push_to_hub(HUB_MODEL_ID)\n",
460
- "# tokenizer.push_to_hub(HUB_MODEL_ID)"
461
- ]
462
- },
463
- {
464
- "cell_type": "markdown",
465
- "metadata": {},
466
- "source": [
467
- "## \ud83d\udd1f Inference Demo \u2013 Qwen3 Thinking Toggle\n",
468
- "\n",
469
- "| Mode | Use Case | Speed |\n",
470
- "|------|----------|-------|\n",
471
- "| `enable_thinking=True` | Deep reasoning, analysis | Slower, thorough |\n",
472
- "| `enable_thinking=False` | Quick answers, coding | Fast, direct |"
473
- ]
474
- },
475
- {
476
- "cell_type": "code",
477
- "execution_count": null,
478
- "metadata": {},
479
- "outputs": [],
480
- "source": [
481
- "FastLanguageModel.for_inference(model)\n",
482
- "\n",
483
- "test_prompt = \"Explain how parameterized queries prevent SQL injection, with a Python example.\"\n",
484
- "\n",
485
- "messages = [\n",
486
- " {\"role\": \"system\", \"content\": \"You are a helpful and knowledgeable assistant.\"},\n",
487
- " {\"role\": \"user\", \"content\": test_prompt},\n",
488
- "]\n",
489
- "\n",
490
- "for think_mode in [True, False]:\n",
491
- " label = \"\ud83e\udde0 THINKING=ON\" if think_mode else \"\u26a1 THINKING=OFF\"\n",
492
- " print(f\"\\n{'='*60}\")\n",
493
- " print(label)\n",
494
- " print(f\"{'='*60}\")\n",
495
- "\n",
496
- " inputs = tokenizer.apply_chat_template(\n",
497
- " messages, tokenize=True, add_generation_prompt=True,\n",
498
- " enable_thinking=think_mode, return_tensors=\"pt\",\n",
499
- " ).to(model.device)\n",
500
- "\n",
501
- " outputs = model.generate(\n",
502
- " input_ids=inputs, max_new_tokens=512, temperature=0.7,\n",
503
- " top_p=0.9, do_sample=True,\n",
504
- " pad_token_id=tokenizer.pad_token_id,\n",
505
- " eos_token_id=tokenizer.eos_token_id,\n",
506
- " )\n",
507
- " reply = tokenizer.decode(outputs[0], skip_special_tokens=True)\n",
508
- " print(reply.split(\"assistant\")[-1].strip()[:800])\n",
509
- " print(f\"\\n[Tokens: {len(outputs[0]) - len(inputs[0])}]\")"
510
- ]
511
- },
512
- {
513
- "cell_type": "markdown",
514
- "metadata": {},
515
- "source": [
516
- "---\n",
517
- "## \ud83d\udcda Dataset & Model References\n",
518
- "\n",
519
- "| Resource | Link |\n",
520
- "|----------|------|\n",
521
- "| **Qwen3-4B-Instruct-2507** | https://huggingface.co/Qwen/Qwen3-4B-Instruct-2507 |\n",
522
- "| **UltraChat 200K** | https://huggingface.co/datasets/HuggingFaceH4/ultrachat_200k |\n",
523
- "| **OpenHermes 2.5** | https://huggingface.co/datasets/teknium/OpenHermes-2.5 |\n",
524
- "| **ShareGPT Multilingual** | https://huggingface.co/datasets/deepmage121/ShareGPT_multilingual |\n",
525
- "| **Code Corpus LLM Training** | https://huggingface.co/datasets/krystv/code-corpus-llm-training |\n",
526
- "| **Fenrir Cybersecurity** | https://huggingface.co/datasets/AlicanKiraz0/Cybersecurity-Dataset-Fenrir-v2.1 |\n",
527
- "| **Trendyol Cybersecurity** | https://huggingface.co/datasets/Trendyol/Trendyol-Cybersecurity-Instruction-Tuning-Dataset |\n",
528
- "| **Unsloth Docs** | https://unsloth.ai/docs |\n",
529
- "\n",
530
- "---\n",
531
- "*Pick any dataset. Train anything. Use responsibly.*"
532
- ]
533
- }
534
  ],
535
- "metadata": {
536
- "kernelspec": {
537
- "display_name": "Python 3",
538
- "language": "python",
539
- "name": "python3"
540
- },
541
- "language_info": {
542
- "name": "python",
543
- "version": "3.10.12"
544
- }
545
- },
546
- "nbformat": 4,
547
- "nbformat_minor": 4
548
- }
 
1
  {
2
  "cells": [
3
+ {"cell_type":"markdown","metadata":{},"source":["# 🔐 Qwen3-4B Unsloth QLoRA — Root-Fixed Kaggle/Colab Notebook\n","\n","Keeps **Unsloth** for low VRAM, but fixes the repeated `AttributeError: 'int' object has no attribute 'mean'` from the root.\n","\n","Root fixes:\n","1. Deletes `/kaggle/working/unsloth_compiled_cache`.\n","2. Updates `unsloth` + `unsloth_zoo` together.\n","3. Forces one kernel restart after install.\n","4. Uses current TRL `SFTConfig` + `processing_class=tokenizer`.\n","5. Sets `trainer.model_accepts_loss_kwargs = False`.\n","\n","> Qwen3-4B is stronger than LFM2.5 but tighter on T4. Defaults are conservative.\n"]},
4
+ {"cell_type":"markdown","metadata":{},"source":["## 1. Clean install Unsloth stack — run first\n"]},
5
+ {"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["import os, sys, shutil, subprocess, pathlib\n","work_dir = pathlib.Path('/kaggle/working') if pathlib.Path('/kaggle/working').exists() else pathlib.Path('/content')\n","marker = work_dir / '.bex_unsloth_env_ready_v3'\n","shutil.rmtree(str(work_dir / 'unsloth_compiled_cache'), ignore_errors=True)\n","print('✅ Removed stale unsloth_compiled_cache')\n","if not marker.exists():\n"," print('Installing/updating Unsloth stack. Kernel will restart after this cell. Run all cells again after restart.')\n"," subprocess.check_call([sys.executable, '-m', 'pip', 'uninstall', '-y', 'unsloth', 'unsloth_zoo'])\n"," subprocess.check_call([sys.executable, '-m', 'pip', 'install', '-U', '--no-cache-dir', 'unsloth', 'unsloth_zoo'])\n"," marker.write_text('ready')\n"," print('✅ Installed Unsloth. Restarting kernel now...')\n"," os.kill(os.getpid(), 9)\n","else:\n"," print('✅ Unsloth stack already prepared for this session')\n"]},
6
+ {"cell_type":"markdown","metadata":{},"source":["## 2. Optional HF login\n"]},
7
+ {"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["from huggingface_hub import login\n","# login(token='hf_YOUR_WRITE_TOKEN')\n"]},
8
+ {"cell_type":"markdown","metadata":{},"source":["## 3. Imports and version check\n"]},
9
+ {"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["from unsloth import FastLanguageModel, is_bfloat16_supported\n","from trl import SFTTrainer, SFTConfig\n","from datasets import load_dataset, concatenate_datasets\n","import torch, random, os, time\n","import transformers, trl, peft, accelerate\n","try:\n"," import unsloth\n"," print('unsloth', getattr(unsloth, '__version__', 'unknown'))\n","except Exception as e:\n"," print('unsloth version unavailable', e)\n","print('transformers', transformers.__version__)\n","print('trl', trl.__version__)\n","print('peft', peft.__version__)\n","print('accelerate', accelerate.__version__)\n","if torch.cuda.is_available():\n"," print('GPU:', torch.cuda.get_device_name(0))\n"," print(f'VRAM: {torch.cuda.get_device_properties(0).total_memory/1e9:.2f} GB')\n","else:\n"," print('⚠️ No GPU found. Enable GPU runtime.')\n"]},
10
+ {"cell_type":"markdown","metadata":{},"source":["## 4. Configuration\n"]},
11
+ {"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["MODEL_ID = 'unsloth/Qwen3-4B-Instruct-2507-unsloth-bnb-4bit'\n","RUN_NAME = 'qwen3-4b-cyber-unsloth-rootfixed'\n","DATASET_CHOICE = 'cybersecurity' # cybersecurity | ultrachat | openhermes | code_corpus\n","SAMPLE_SIZE = 30000\n","MAX_SEQ_LENGTH = 2048\n","LORA_R = 32\n","LORA_ALPHA = 64\n","BATCH_SIZE = 1\n","GRAD_ACCUM = 8\n","MAX_STEPS = 1500\n","LEARNING_RATE = 1.5e-4\n","WARMUP_STEPS = 100\n","SAVE_STEPS = 500\n","LOGGING_STEPS = 10\n","PACKING = False\n","SEED = 3407\n","OUTPUT_DIR = './outputs_qwen3_rootfixed'\n","HUB_MODEL_ID = 'your-username/qwen3-4b-cyber-unsloth-rootfixed'\n","PUSH_TO_HUB = False\n","random.seed(SEED)\n","torch.manual_seed(SEED)\n","if torch.cuda.is_available(): torch.cuda.manual_seed_all(SEED)\n"]},
12
+ {"cell_type":"markdown","metadata":{},"source":["## 5. Load model with Unsloth 4-bit QLoRA\n"]},
13
+ {"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["model, tokenizer = FastLanguageModel.from_pretrained(\n"," model_name=MODEL_ID,\n"," max_seq_length=MAX_SEQ_LENGTH,\n"," dtype=None,\n"," load_in_4bit=True,\n"," device_map={'': torch.cuda.current_device()} if torch.cuda.is_available() else None,\n",")\n","if tokenizer.pad_token is None:\n"," tokenizer.pad_token = tokenizer.eos_token\n","tokenizer.padding_side = 'right'\n","model = FastLanguageModel.get_peft_model(\n"," model,\n"," r=LORA_R,\n"," target_modules=['q_proj','k_proj','v_proj','o_proj','gate_proj','up_proj','down_proj'],\n"," lora_alpha=LORA_ALPHA,\n"," lora_dropout=0,\n"," bias='none',\n"," use_gradient_checkpointing='unsloth',\n"," random_state=SEED,\n"," use_rslora=True,\n"," loftq_config=None,\n",")\n","trainable = sum(p.numel() for p in model.parameters() if p.requires_grad)\n","total = sum(p.numel() for p in model.parameters())\n","print(f'Trainable params: {trainable:,} / {total:,} ({100*trainable/total:.2f}%)')\n","if torch.cuda.is_available():\n"," print(f'VRAM after model load: {torch.cuda.memory_allocated()/1e9:.2f} GB')\n"]},
14
+ {"cell_type":"markdown","metadata":{},"source":["## 6. Load and format dataset\n"]},
15
+ {"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["SYSTEM_SAFE = 'You are a cybersecurity education assistant. Provide defensive, ethical, and authorized security guidance only. Refuse harmful or unauthorized requests.'\n","def convert_sua(example):\n"," return {'messages': [{'role':'system','content':example.get('system') or SYSTEM_SAFE},{'role':'user','content':example['user']},{'role':'assistant','content':example['assistant']}]}\n","def convert_ultrachat(example): return {'messages': example['messages']}\n","def convert_conversations(example):\n"," msgs=[]\n"," sys_prompt=example.get('system_prompt','') or example.get('system','')\n"," if sys_prompt: msgs.append({'role':'system','content':sys_prompt})\n"," for turn in example['conversations']:\n"," role='user' if turn.get('from') in ('human','user') else 'assistant'\n"," msgs.append({'role':role,'content':turn.get('value','')})\n"," return {'messages':msgs}\n","def convert_code(example):\n"," return {'messages':[{'role':'user','content':'Explain and improve this code with attention to safety and correctness.'},{'role':'assistant','content':example['text']}]}\n","if DATASET_CHOICE == 'cybersecurity':\n"," ds1=load_dataset('AlicanKiraz0/Cybersecurity-Dataset-Fenrir-v2.1', split='train').map(convert_sua, remove_columns=['system','user','assistant'])\n"," ds2=load_dataset('Trendyol/Trendyol-Cybersecurity-Instruction-Tuning-Dataset', split='train').map(convert_sua, remove_columns=['system','user','assistant'])\n"," dataset=concatenate_datasets([ds1,ds2])\n","elif DATASET_CHOICE == 'ultrachat':\n"," dataset=load_dataset('HuggingFaceH4/ultrachat_200k', split='train_sft')\n"," dataset=dataset.map(convert_ultrachat, remove_columns=dataset.column_names)\n","elif DATASET_CHOICE == 'openhermes':\n"," dataset=load_dataset('teknium/OpenHermes-2.5', split='train')\n"," dataset=dataset.map(convert_conversations, remove_columns=dataset.column_names)\n","elif DATASET_CHOICE == 'code_corpus':\n"," dataset=load_dataset('krystv/code-corpus-llm-training', split='train')\n"," dataset=dataset.map(convert_code, remove_columns=dataset.column_names)\n","else: raise ValueError(DATASET_CHOICE)\n","print('Rows:', len(dataset))\n","if SAMPLE_SIZE and len(dataset)>SAMPLE_SIZE:\n"," dataset=dataset.shuffle(seed=SEED).select(range(SAMPLE_SIZE))\n"," print('Subsampled:', len(dataset))\n","print(dataset[0]['messages'])\n"]},
16
+ {"cell_type":"markdown","metadata":{},"source":["## 7. Convert messages to text\n"]},
17
+ {"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["def to_text(example):\n"," try:\n"," text=tokenizer.apply_chat_template(example['messages'], tokenize=False, add_generation_prompt=False)\n"," except Exception:\n"," text='\\n'.join([f\"<{m['role']}>\\n{m['content']}\\n</{m['role']}>\" for m in example['messages']]) + tokenizer.eos_token\n"," return {'text': text}\n","dataset=dataset.map(to_text, remove_columns=['messages'])\n","print(dataset[0]['text'][:500])\n"]},
18
+ {"cell_type":"markdown","metadata":{},"source":["## 8. Train with current SFTConfig API\n"]},
19
+ {"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["args = SFTConfig(\n"," output_dir=OUTPUT_DIR, dataset_text_field='text', max_length=MAX_SEQ_LENGTH, packing=PACKING,\n"," per_device_train_batch_size=BATCH_SIZE, gradient_accumulation_steps=GRAD_ACCUM,\n"," warmup_steps=WARMUP_STEPS, max_steps=MAX_STEPS, learning_rate=LEARNING_RATE,\n"," fp16=not is_bfloat16_supported(), bf16=is_bfloat16_supported(), logging_steps=LOGGING_STEPS,\n"," optim='adamw_8bit', weight_decay=0.01, lr_scheduler_type='linear', seed=SEED,\n"," save_strategy='steps', save_steps=SAVE_STEPS, save_total_limit=2, report_to='none',\n"," disable_tqdm=True, logging_first_step=True,\n",")\n","trainer = SFTTrainer(model=model, processing_class=tokenizer, train_dataset=dataset, args=args)\n","trainer.model_accepts_loss_kwargs = False\n","if hasattr(trainer, 'model'): trainer.model.config.use_cache = False\n","if torch.cuda.is_available(): print(f'VRAM before train: {torch.cuda.memory_allocated()/1e9:.2f} GB / {torch.cuda.get_device_properties(0).total_memory/1e9:.2f} GB')\n","trainer_stats = trainer.train()\n","print(trainer_stats)\n"]},
20
+ {"cell_type":"markdown","metadata":{},"source":["## 9. Save adapter and test\n"]},
21
+ {"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["model.save_pretrained('./qwen3-lora-adapter')\n","tokenizer.save_pretrained('./qwen3-lora-adapter')\n","print('Saved adapter to ./qwen3-lora-adapter')\n","if PUSH_TO_HUB:\n"," model.push_to_hub(HUB_MODEL_ID); tokenizer.push_to_hub(HUB_MODEL_ID)\n","FastLanguageModel.for_inference(model)\n","messages=[{'role':'system','content':SYSTEM_SAFE},{'role':'user','content':'Explain parameterized queries for preventing SQL injection with safe Python.'}]\n","inputs=tokenizer.apply_chat_template(messages, tokenize=True, add_generation_prompt=True, return_tensors='pt').to(model.device)\n","with torch.no_grad():\n"," outputs=model.generate(input_ids=inputs, max_new_tokens=384, temperature=0.7, top_p=0.9, do_sample=True, pad_token_id=tokenizer.pad_token_id, eos_token_id=tokenizer.eos_token_id)\n","print(tokenizer.decode(outputs[0][inputs.shape[1]:], skip_special_tokens=True))\n"]}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
22
  ],
23
+ "metadata":{"kernelspec":{"display_name":"Python 3","language":"python","name":"python3"},"language_info":{"name":"python","version":"3.10"}},
24
+ "nbformat":4,
25
+ "nbformat_minor":5
26
+ }