{ "nbformat": 4, "nbformat_minor": 5, "metadata": { "colab": { "provenance": [], "gpuType": "T4", "name": "Network_Admin_LLM_Training.ipynb" }, "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.10.12" }, "accelerator": "GPU" }, "cells": [ { "cell_type": "markdown", "metadata": { "id": "title" }, "source": [ "# 🚀 Network Admin LLM - QLoRA Fine-tuning\n", "\n", "**基礎模型**: microsoft/Phi-4-mini-instruct (3.8B)\n", "\n", "**訓練方法**: QLoRA SFT (4-bit 量化 + LoRA)\n", "\n", "**數據集**: NetEval (5732題) + Telecom Intent Config (10K) ≈ 15K 樣本\n", "\n", "**訓練時間**: ~2-3 小時 (T4 GPU)\n", "\n", "---\n", "\n", "⚠️ **請先設定 GPU Runtime**: Runtime → Change runtime type → T4 GPU" ] }, { "cell_type": "markdown", "metadata": { "id": "step1" }, "source": [ "## Step 1. 安裝依賴" ] }, { "cell_type": "code", "metadata": { "id": "install" }, "execution_count": null, "outputs": [], "source": [ "!pip install -q transformers trl peft bitsandbytes accelerate datasets trackio\n", "\n", "import torch\n", "print(f'PyTorch: {torch.__version__}')\n", "print(f'CUDA: {torch.cuda.is_available()}')\n", "if torch.cuda.is_available():\n", " print(f'GPU: {torch.cuda.get_device_name(0)}')\n", " print(f'VRAM: {torch.cuda.get_device_properties(0).total_mem / 1024**3:.1f} GB')\n", "print('✅ Dependencies installed')" ] }, { "cell_type": "markdown", "metadata": { "id": "step2" }, "source": [ "## Step 2. HuggingFace 登入\n", "\n", "請先到 https://huggingface.co/settings/tokens 取得你的 Access Token(需要 Write 權限)" ] }, { "cell_type": "code", "metadata": { "id": "login" }, "execution_count": null, "outputs": [], "source": [ "from huggingface_hub import login\n", "\n", "# 方法 1: 直接輸入 token\n", "# login(token=\"hf_xxxxxxxxxxxxxxxxxxxx\")\n", "\n", "# 方法 2: 互動式登入 (推薦)\n", "login()" ] }, { "cell_type": "markdown", "metadata": { "id": "step3" }, "source": [ "## Step 3. 配置設定\n", "\n", "請修改 `HF_USERNAME` 為你的 HuggingFace 用戶名" ] }, { "cell_type": "code", "metadata": { "id": "config" }, "execution_count": null, "outputs": [], "source": [ "# ======== 請修改這裡 ========\n", "HF_USERNAME = \"YOUR_HF_USERNAME\" # 改成你的 HuggingFace 用戶名\n", "# ============================\n", "\n", "MODEL_NAME = \"microsoft/Phi-4-mini-instruct\"\n", "OUTPUT_DIR = f\"{HF_USERNAME}/network-admin-phi4-mini\"\n", "\n", "TRAINING_CONFIG = {\n", " \"learning_rate\": 2e-4,\n", " \"num_epochs\": 3,\n", " \"batch_size\": 4,\n", " \"gradient_accumulation\": 4,\n", " \"max_seq_length\": 2048,\n", " \"lora_r\": 16,\n", " \"lora_alpha\": 32,\n", "}\n", "\n", "print(f'模型: {MODEL_NAME}')\n", "print(f'輸出: https://huggingface.co/{OUTPUT_DIR}')" ] }, { "cell_type": "markdown", "metadata": { "id": "step4" }, "source": [ "## Step 4. 載入數據集" ] }, { "cell_type": "code", "metadata": { "id": "data" }, "execution_count": null, "outputs": [], "source": [ "import os\n", "import torch\n", "from datasets import load_dataset, concatenate_datasets\n", "\n", "# --- NetEval 網管考試題庫 ---\n", "print('📚 載入 NetEval 網管考試題庫...')\n", "neteval_dataset = load_dataset('NASP/neteval-exam', split='train')\n", "print(f' NetEval: {len(neteval_dataset)} 題')\n", "\n", "def convert_neteval(example):\n", " question = example['Question']\n", " options = (\n", " f\"\\nA. {example.get('A', '')}\"\n", " f\"\\nB. {example.get('B', '')}\"\n", " f\"\\nC. {example.get('C', '')}\"\n", " f\"\\nD. {example.get('D', '')}\"\n", " )\n", " answer = f\"正確答案是: {example['Answer']}\"\n", " if example.get('Explanation'):\n", " answer += f\"\\n\\n📖 解說: {example['Explanation']}\"\n", " return {\n", " 'messages': [\n", " {'role': 'system', 'content': '你是一位網路管理專家。請回答關於網路、安全、路由、交換機、VLAN、防火牆等IT基礎設施的問題。'},\n", " {'role': 'user', 'content': f'{question}{options}'},\n", " {'role': 'assistant', 'content': answer}\n", " ]\n", " }\n", "\n", "neteval_converted = neteval_dataset.map(\n", " convert_neteval,\n", " remove_columns=neteval_dataset.column_names\n", ")\n", "\n", "# --- Telecom 電信意圖配置數據 ---\n", "print('📚 載入 Telecom 電信意圖配置數據...')\n", "telecom_dataset = load_dataset('nraptisss/telecom-intent-config-sft-10k', split='train')\n", "telecom_messages = telecom_dataset.map(\n", " lambda x: {'messages': x['messages']},\n", " remove_columns=[c for c in telecom_dataset.column_names if c != 'messages']\n", ")\n", "print(f' Telecom: {len(telecom_messages)} 條')\n", "\n", "# --- 合併數據集 ---\n", "combined = concatenate_datasets([neteval_converted, telecom_messages])\n", "split_data = combined.train_test_split(test_size=0.1, seed=42)\n", "train_ds = split_data['train']\n", "eval_ds = split_data['test']\n", "\n", "print(f'\\n📊 總計: {len(train_ds)} train / {len(eval_ds)} eval')" ] }, { "cell_type": "markdown", "metadata": { "id": "step5" }, "source": [ "## Step 5. 載入模型與 QLoRA" ] }, { "cell_type": "code", "metadata": { "id": "model" }, "execution_count": null, "outputs": [], "source": [ "from transformers import AutoTokenizer, AutoModelForCausalLM, BitsAndBytesConfig\n", "from peft import LoraConfig, prepare_model_for_kbit_training, get_peft_model\n", "\n", "# Tokenizer\n", "tokenizer = AutoTokenizer.from_pretrained(MODEL_NAME)\n", "tokenizer.pad_token = tokenizer.eos_token\n", "print(f'Tokenizer vocab: {len(tokenizer):,}')\n", "\n", "# 4-bit 量化配置\n", "bnb_config = BitsAndBytesConfig(\n", " load_in_4bit=True,\n", " bnb_4bit_quant_type='nf4',\n", " bnb_4bit_compute_dtype=torch.bfloat16,\n", " bnb_4bit_use_double_quant=True,\n", ")\n", "\n", "# 載入模型\n", "print('📥 載入模型 (4-bit)...')\n", "model = AutoModelForCausalLM.from_pretrained(\n", " MODEL_NAME,\n", " quantization_config=bnb_config,\n", " device_map='auto',\n", " trust_remote_code=True,\n", ")\n", "model = prepare_model_for_kbit_training(model)\n", "print('✅ 模型載入完成')\n", "\n", "# LoRA 配置\n", "lora_config = LoraConfig(\n", " r=TRAINING_CONFIG['lora_r'],\n", " lora_alpha=TRAINING_CONFIG['lora_alpha'],\n", " lora_dropout=0.05,\n", " bias='none',\n", " task_type='CAUSAL_LM',\n", " target_modules=['q_proj', 'k_proj', 'v_proj', 'o_proj', 'gate_proj', 'up_proj', 'down_proj'],\n", " modules_to_save=['lm_head', 'embed_tokens'],\n", ")\n", "\n", "model = get_peft_model(model, lora_config)\n", "model.print_trainable_parameters()" ] }, { "cell_type": "markdown", "metadata": { "id": "step6" }, "source": [ "## Step 6. 開始訓練\n", "\n", "☕ 這步驟約需 2-3 小時,可以去喝杯咖啡" ] }, { "cell_type": "code", "metadata": { "id": "train" }, "execution_count": null, "outputs": [], "source": [ "from trl import SFTTrainer, SFTConfig\n", "\n", "training_args = SFTConfig(\n", " learning_rate=TRAINING_CONFIG['learning_rate'],\n", " lr_scheduler_type='cosine',\n", " warmup_ratio=0.1,\n", " num_train_epochs=TRAINING_CONFIG['num_epochs'],\n", " per_device_train_batch_size=TRAINING_CONFIG['batch_size'],\n", " gradient_accumulation_steps=TRAINING_CONFIG['gradient_accumulation'],\n", " max_seq_length=TRAINING_CONFIG['max_seq_length'],\n", " gradient_checkpointing=True,\n", " bf16=True,\n", " output_dir='./output',\n", " logging_steps=10,\n", " save_steps=500,\n", " eval_steps=500,\n", " push_to_hub=True,\n", " hub_model_id=OUTPUT_DIR,\n", " logging_strategy='steps',\n", " logging_first_step=True,\n", ")\n", "\n", "trainer = SFTTrainer(\n", " model=model,\n", " args=training_args,\n", " train_dataset=train_ds,\n", " eval_dataset=eval_ds,\n", " processing_class=tokenizer,\n", " peft_config=lora_config,\n", ")\n", "\n", "print('🚀 開始訓練...')\n", "print(f'訓練完成後模型將保存到: https://huggingface.co/{OUTPUT_DIR}')\n", "trainer.train()" ] }, { "cell_type": "markdown", "metadata": { "id": "step7" }, "source": [ "## Step 7. 上傳模型到 HuggingFace Hub" ] }, { "cell_type": "code", "metadata": { "id": "push" }, "execution_count": null, "outputs": [], "source": [ "trainer.push_to_hub()\n", "print(f'✅ 模型已上傳: https://huggingface.co/{OUTPUT_DIR}')" ] }, { "cell_type": "markdown", "metadata": { "id": "step8" }, "source": [ "## Step 8. 測試推理\n", "\n", "訓練完成後,直接在這裡測試模型:" ] }, { "cell_type": "code", "metadata": { "id": "inference" }, "execution_count": null, "outputs": [], "source": [ "# 測試推理\n", "from transformers import pipeline\n", "\n", "pipe = pipeline(\n", " 'text-generation',\n", " model=model,\n", " tokenizer=tokenizer,\n", " max_new_tokens=512,\n", " do_sample=True,\n", " temperature=0.7,\n", ")\n", "\n", "# 測試問題\n", "test_questions = [\n", " '什麼是 VLAN?如何配置?',\n", " 'OSPF 和 BGP 的區別是什麼?',\n", " '如何排查網路連接問題?',\n", "]\n", "\n", "for q in test_questions:\n", " print(f'\\n{\"=\"*50}')\n", " print(f'💬 問: {q}')\n", " print(f'{\"=\"*50}')\n", " messages = [\n", " {'role': 'system', 'content': '你是一位網路管理專家。'},\n", " {'role': 'user', 'content': q}\n", " ]\n", " output = pipe(messages)\n", " reply = output[0]['generated_text'][-1]['content']\n", " print(f'🤖 答: {reply}')" ] }, { "cell_type": "markdown", "metadata": { "id": "done" }, "source": [ "---\n", "\n", "## ✅ 完成!\n", "\n", "你的網管 LLM 已經訓練完成並上傳到 HuggingFace Hub。\n", "\n", "### 在其他地方使用這個模型:\n", "\n", "```python\n", "from peft import PeftModel\n", "from transformers import AutoModelForCausalLM, AutoTokenizer, BitsAndBytesConfig\n", "import torch\n", "\n", "bnb_config = BitsAndBytesConfig(\n", " load_in_4bit=True,\n", " bnb_4bit_quant_type='nf4',\n", " bnb_4bit_compute_dtype=torch.bfloat16,\n", ")\n", "\n", "base_model = AutoModelForCausalLM.from_pretrained(\n", " 'microsoft/Phi-4-mini-instruct',\n", " quantization_config=bnb_config,\n", " device_map='auto',\n", ")\n", "model = PeftModel.from_pretrained(base_model, 'YOUR_USERNAME/network-admin-phi4-mini')\n", "tokenizer = AutoTokenizer.from_pretrained('microsoft/Phi-4-mini-instruct')\n", "\n", "messages = [{'role': 'user', 'content': '什麼是 VLAN?'}]\n", "text = tokenizer.apply_chat_template(messages, tokenize=False, add_generation_prompt=True)\n", "inputs = tokenizer(text, return_tensors='pt').to('cuda')\n", "outputs = model.generate(**inputs, max_new_tokens=512)\n", "print(tokenizer.decode(outputs[0], skip_special_tokens=True))\n", "```" ] } ] }