Spaces:
Sleeping
Sleeping
| #!/usr/bin/env python3 | |
| # -*- coding: utf-8 -*- | |
| ''' | |
| Network Admin LLM - QLoRA Fine-tuning Script | |
| ============================================= | |
| Base Model: microsoft/Phi-4-mini-instruct | |
| Method: QLoRA SFT (4-bit quantization + LoRA) | |
| Datasets: NetEval + Telecom Intent Config | |
| Run locally with GPU: | |
| pip install transformers trl peft bitsandbytes accelerate datasets trackio | |
| python network_admin_llm_train.py | |
| Or on Google Colab: | |
| !pip install transformers trl peft bitsandbytes accelerate datasets trackio | |
| %cd /content | |
| !python network_admin_llm_train.py | |
| Author: Network Admin LLM Project | |
| ''' | |
| import os | |
| import sys | |
| import torch | |
| from datetime import datetime | |
| # ============== CONFIGURATION ============== | |
| # ่ซไฟฎๆนไปฅไธ่จญๅฎ | |
| MODEL_NAME = 'microsoft/Phi-4-mini-instruct' | |
| HF_USERNAME = 'YOUR_HF_USERNAME' # ๆนๆไฝ ็ HuggingFace ็จๆถๅ | |
| HF_TOKEN = os.environ.get('HF_TOKEN', 'YOUR_HF_TOKEN') # HF token for upload | |
| # ่จ็ทด่ถ ๅๆธ | |
| TRAINING_CONFIG = { | |
| 'learning_rate': 2e-4, # LoRA ้่ฆ่ผ้ซๅญธ็ฟ็ | |
| 'num_epochs': 3, | |
| 'batch_size': 4, | |
| 'gradient_accumulation': 4, # effective batch = 16 | |
| 'max_seq_length': 2048, | |
| 'lora_r': 16, | |
| 'lora_alpha': 32, | |
| 'lora_dropout': 0.05, | |
| 'warmup_ratio': 0.1, | |
| } | |
| OUTPUT_DIR = f'{HF_USERNAME}/network-admin-phi4-mini' | |
| # =========================================== | |
| def print_section(title): | |
| print(f'\n{"="*60}') | |
| print(f' {title}') | |
| print('='*60) | |
| def install_dependencies(): | |
| '''ๆชขๆฅไธฆๅฎ่ฃไพ่ณด''' | |
| print_section('CHECKING DEPENDENCIES') | |
| required = ['transformers', 'trl', 'peft', 'bitsandbytes', 'accelerate', 'datasets', 'trackio'] | |
| missing = [] | |
| for pkg in required: | |
| try: | |
| __import__(pkg.replace('-', '_')) | |
| print(f'โ {pkg}') | |
| except ImportError: | |
| missing.append(pkg) | |
| print(f'โ {pkg} - ้่ฆๅฎ่ฃ') | |
| if missing: | |
| print(f'\n่ซ้่ก: pip install {" ".join(missing)}') | |
| return False | |
| return True | |
| def load_and_prepare_datasets(): | |
| '''่ผๅ ฅไธฆ่ฝๆๆธๆ้''' | |
| from datasets import load_dataset, concatenate_datasets | |
| print_section('LOADING DATASETS') | |
| # 1. ่ผๅ ฅ NetEval ่่ฉฆ้กๅบซ | |
| print('๐ ่ผๅ ฅ NetEval ่่ฉฆ้กๅบซ...') | |
| neteval_dataset = load_dataset('NASP/neteval-exam', split='train') | |
| print(f' NetEval: {len(neteval_dataset)} ้ก') | |
| def convert_neteval(example): | |
| '''ๅฐ Q&A ๆ ผๅผ่ฝๆ็บๅฐ่ฉฑๆ ผๅผ''' | |
| question = example['Question'] | |
| options = f'\nA. {example.get("A", "")}\nB. {example.get("B", "")}\nC. {example.get("C", "")}\nD. {example.get("D", "")}' | |
| answer = f'ๆญฃ็ขบ็ญๆกๆฏ: {example["Answer"]}' | |
| if example.get('Explanation'): | |
| answer += f'\n\n๐ ่งฃ่ชช: {example["Explanation"]}' | |
| return { | |
| 'messages': [ | |
| {'role': 'system', 'content': 'ไฝ ๆฏไธไฝ็ถฒ่ทฏ็ฎก็ๅฐๅฎถใ่ซๅ็ญ้ๆผ็ถฒ่ทฏใๅฎๅ จใ่ทฏ็ฑใไบคๆๆฉใVLANใ้ฒ็ซ็็ญITๅบ็ค่จญๆฝ็ๅ้กใ'}, | |
| {'role': 'user', 'content': f'{question}{options}'}, | |
| {'role': 'assistant', 'content': answer} | |
| ] | |
| } | |
| neteval_converted = neteval_dataset.map( | |
| convert_neteval, | |
| remove_columns=neteval_dataset.column_names, | |
| desc='่ฝๆ NetEval ๆ ผๅผ' | |
| ) | |
| # 2. ่ผๅ ฅ้ปไฟกๆๅ้ ็ฝฎๆธๆ้ | |
| print('๐ ่ผๅ ฅ้ปไฟกๆๅ้ ็ฝฎๆธๆ้...') | |
| telecom_dataset = load_dataset('nraptisss/telecom-intent-config-sft-10k', split='train') | |
| print(f' Telecom: {len(telecom_dataset)} ๆข') | |
| telecom_messages = telecom_dataset.map( | |
| lambda x: {'messages': x['messages']}, | |
| remove_columns=[c for c in telecom_dataset.column_names if c != 'messages'] | |
| ) | |
| # 3. ๅไฝตๆธๆ้ | |
| print('๐ ๅไฝตๆธๆ้...') | |
| combined = concatenate_datasets([neteval_converted, telecom_messages]) | |
| split_data = combined.train_test_split(test_size=0.1, seed=42) | |
| train_ds = split_data['train'] | |
| eval_ds = split_data['test'] | |
| print(f'\n๐ ๆธๆ้็ตฑ่จ:') | |
| print(f' ่จ็ทด้: {len(train_ds)} ๆข') | |
| print(f' ้ฉ่ญ้: {len(eval_ds)} ๆข') | |
| print(f' ็ธฝ่จ: {len(combined)} ๆข') | |
| return train_ds, eval_ds | |
| def setup_model_and_tokenizer(): | |
| '''่จญ็ฝฎๆจกๅๅ tokenizer''' | |
| from transformers import AutoTokenizer, AutoModelForCausalLM, BitsAndBytesConfig | |
| from peft import LoraConfig, prepare_model_for_kbit_training, get_peft_model | |
| print_section('LOADING MODEL') | |
| print(f'๐ค ๆจกๅ: {MODEL_NAME}') | |
| # Tokenizer | |
| print('\n๐ ่ผๅ ฅ Tokenizer...') | |
| tokenizer = AutoTokenizer.from_pretrained(MODEL_NAME) | |
| tokenizer.pad_token = tokenizer.eos_token | |
| tokenizer.padding_side = 'right' | |
| print(f' Vocab size: {len(tokenizer):,}') | |
| # QLoRA ้ ็ฝฎ (4-bit) | |
| print('\nโก ้ ็ฝฎ QLoRA (4-bit)...') | |
| bnb_config = BitsAndBytesConfig( | |
| load_in_4bit=True, | |
| bnb_4bit_quant_type='nf4', # Normalized Float4 | |
| bnb_4bit_compute_dtype=torch.bfloat16, | |
| bnb_4bit_use_double_quant=True, # ๅตๅฅ้ๅ | |
| ) | |
| # ่ผๅ ฅๆจกๅ | |
| print('๐ฅ ่ผๅ ฅๆจกๅ (4-bit)...') | |
| model = AutoModelForCausalLM.from_pretrained( | |
| MODEL_NAME, | |
| quantization_config=bnb_config, | |
| device_map='auto', | |
| trust_remote_code=True, | |
| ) | |
| # ๆบๅ kbit ่จ็ทด | |
| model = prepare_model_for_kbit_training(model) | |
| print('โ ๆจกๅๆบๅๅฎๆ') | |
| # LoRA ้ ็ฝฎ | |
| print('\n๐ง ้ ็ฝฎ LoRA...') | |
| lora_config = LoraConfig( | |
| r=TRAINING_CONFIG['lora_r'], | |
| lora_alpha=TRAINING_CONFIG['lora_alpha'], | |
| lora_dropout=TRAINING_CONFIG['lora_dropout'], | |
| bias='none', | |
| task_type='CAUSAL_LM', | |
| target_modules=[ | |
| 'q_proj', 'k_proj', 'v_proj', 'o_proj', # Attention | |
| 'gate_proj', 'up_proj', 'down_proj', # MLP | |
| ], | |
| modules_to_save=['lm_head', 'embed_tokens'], | |
| ) | |
| # ๆ็จ LoRA | |
| model = get_peft_model(model, lora_config) | |
| model.print_trainable_parameters() | |
| return model, tokenizer, lora_config | |
| def setup_trainer(model, tokenizer, train_ds, eval_ds, lora_config): | |
| '''่จญ็ฝฎ่จ็ทดๅจ''' | |
| from trl import SFTTrainer, SFTConfig | |
| print_section('CONFIGURING TRAINER') | |
| # ็ๆ้่กๅ็จฑ | |
| run_name = f'phi4-netadmin-{datetime.now().strftime("%m%d-%H%M")}' | |
| # ๅ่ฉฆๅๅงๅ trackio | |
| try: | |
| import trackio | |
| trackio.init(project='network-admin-llm', experiment='qlora-sft', run_name=run_name) | |
| print('โ Trackio ๅๅงๅๆๅ') | |
| report_to = ['trackio'] | |
| except Exception as e: | |
| print(f'โ ๏ธ Trackio ๅๅงๅๅคฑๆ: {e}') | |
| report_to = ['none'] | |
| # SFT ้ ็ฝฎ | |
| training_args = SFTConfig( | |
| # ๅญธ็ฟ็ | |
| learning_rate=TRAINING_CONFIG['learning_rate'], | |
| lr_scheduler_type='cosine', | |
| warmup_ratio=TRAINING_CONFIG['warmup_ratio'], | |
| # ่จ็ทด | |
| num_train_epochs=TRAINING_CONFIG['num_epochs'], | |
| per_device_train_batch_size=TRAINING_CONFIG['batch_size'], | |
| gradient_accumulation_steps=TRAINING_CONFIG['gradient_accumulation'], | |
| max_seq_length=TRAINING_CONFIG['max_seq_length'], | |
| # ่จๆถ้ซๅชๅ | |
| gradient_checkpointing=True, | |
| bf16=True, | |
| fp16=False, | |
| # ่ผธๅบ | |
| output_dir='./output', | |
| logging_steps=10, | |
| save_steps=500, | |
| save_total_limit=2, | |
| evaluation_strategy='steps', | |
| eval_steps=500, | |
| # Hub ไธๅณ | |
| push_to_hub=True, | |
| hub_model_id=OUTPUT_DIR, | |
| hub_strategy='checkpoint', | |
| # ็ฃๆง | |
| report_to=report_to, | |
| logging_strategy='steps', | |
| logging_first_step=True, | |
| # ้้ | |
| remove_unused_columns=False, | |
| dataloader_num_workers=4, | |
| seed=42, | |
| ) | |
| # ๅตๅปบ trainer | |
| trainer = SFTTrainer( | |
| model=model, | |
| args=training_args, | |
| train_dataset=train_ds, | |
| eval_dataset=eval_ds, | |
| processing_class=tokenizer, | |
| peft_config=lora_config, | |
| ) | |
| return trainer, run_name | |
| def train_model(trainer): | |
| '''ๅท่ก่จ็ทด''' | |
| print_section('STARTING TRAINING') | |
| print('๐ ้ๅง่จ็ทด...') | |
| print(' (ๆ Ctrl+C ๅฏ้จๆไธญๆท)') | |
| print() | |
| try: | |
| trainer.train() | |
| print('\nโ ่จ็ทดๅฎๆ!') | |
| return True | |
| except KeyboardInterrupt: | |
| print('\nโ ๏ธ ่จ็ทด่ขซ็จๆถไธญๆท') | |
| return False | |
| except Exception as e: | |
| print(f'\nโ ่จ็ทดๅคฑๆ: {e}') | |
| raise | |
| def save_and_upload(trainer): | |
| '''ไฟๅญไธฆไธๅณๆจกๅ''' | |
| print_section('SAVING & UPLOADING') | |
| try: | |
| print('๐ค ไธๅณๆจกๅๅฐ HuggingFace Hub...') | |
| trainer.push_to_hub() | |
| print(f'\nโ ๆจกๅๅทฒไธๅณ!') | |
| print(f'๐ ้ฃ็ต: https://huggingface.co/{OUTPUT_DIR}') | |
| except Exception as e: | |
| print(f'\nโ ๏ธ ไธๅณๅคฑๆ: {e}') | |
| print('ๆจกๅๅทฒไฟๅญๅจ ./output ็ฎ้') | |
| def main(): | |
| '''ไธปๅฝๆธ''' | |
| print(''' | |
| โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ | |
| โ Network Admin LLM - QLoRA Fine-tuning โ | |
| โ Base: microsoft/Phi-4-mini-instruct โ | |
| โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ | |
| ''') | |
| # ๆชขๆฅ GPU | |
| print(f'๐ฅ๏ธ GPU: {torch.cuda.get_device_name(0) if torch.cuda.is_available() else "็ก GPU"}') | |
| if torch.cuda.is_available(): | |
| print(f' Memory: {torch.cuda.get_device_properties(0).total_memory / 1024**3:.1f} GB') | |
| # ๅฎ่ฃไพ่ณด | |
| if not install_dependencies(): | |
| sys.exit(1) | |
| # ่ผๅ ฅๆธๆ | |
| train_ds, eval_ds = load_and_prepare_datasets() | |
| # ่จญ็ฝฎๆจกๅ | |
| model, tokenizer, lora_config = setup_model_and_tokenizer() | |
| # ่จญ็ฝฎ trainer | |
| trainer, run_name = setup_trainer(model, tokenizer, train_ds, eval_ds, lora_config) | |
| # ่จ็ทด | |
| success = train_model(trainer) | |
| # ไฟๅญ | |
| if success: | |
| save_and_upload(trainer) | |
| print_section('DONE') | |
| print(f'Run name: {run_name}') | |
| if __name__ == '__main__': | |
| main() | |