Spaces:
Sleeping
Sleeping
File size: 8,842 Bytes
55159b1 5f0c197 e03c947 55159b1 930fd33 55159b1 930fd33 55159b1 5f0c197 55159b1 930fd33 b602c40 930fd33 55159b1 930fd33 55159b1 930fd33 55159b1 e03c947 55159b1 5f0c197 55159b1 5f0c197 55159b1 e03c947 55159b1 e03c947 55159b1 e03c947 55159b1 5f0c197 c38b6f2 26efb80 c38b6f2 55159b1 5f0c197 55159b1 5f0c197 55159b1 930fd33 55159b1 930fd33 55159b1 1bbece6 930fd33 55159b1 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 | import os
import sys
import sqlite3
import pandas as pd
import torch
from datasets import load_dataset
from transformers import (
AutoModelForCausalLM,
AutoTokenizer,
BitsAndBytesConfig,
TrainingArguments,
TrainerCallback
)
from peft import LoraConfig, get_peft_model, prepare_model_for_kbit_training
from trl import SFTTrainer, SFTConfig
def adapted_projection_types(peft_model) -> set:
"""Base-module suffixes that actually received a LoRA adapter."""
return {name.rsplit(".lora_A", 1)[0].split(".")[-1]
for name, _ in peft_model.named_modules() if name.endswith("lora_A")}
EXPECTED_TARGETS = {"qkv_proj", "o_proj", "gate_up_proj", "down_proj"}
MAX_LEN = 2560 # training sequence cap; longer examples are dropped so their SQL labels aren't truncated
class VRAMLoggerCallback(TrainerCallback):
"""Callback to print VRAM usage during training to monitor resource consumption."""
def on_step_end(self, args, state, control, **kwargs):
if torch.cuda.is_available():
allocated = torch.cuda.memory_allocated(0) / (1024 ** 3)
reserved = torch.cuda.memory_reserved(0) / (1024 ** 3)
print(f" - [Step {state.global_step}] VRAM Allocated: {allocated:.2f} GB | Reserved: {reserved:.2f} GB")
def train():
print("=" * 60)
print("Starting Phi-3 Text-to-SQL QLoRA Fine-Tuning")
print("=" * 60)
# 1. Device Configuration and Hardware Checks
if not torch.cuda.is_available():
print("WARNING: CUDA is NOT available. Running on CPU is extremely slow and NOT recommended.")
device = "cpu"
use_bf16 = False
use_fp16 = False
else:
device = "cuda"
gpu_name = torch.cuda.get_device_name(0)
print(f"CUDA Device Detected: {gpu_name}")
# Native bf16 only on Ampere+ (compute capability >= 8). Turing (T4, sm_75) reports
# is_bf16_supported()=True but EMULATES bf16 slowly, so gate on the real capability.
major, _ = torch.cuda.get_device_capability()
use_bf16 = major >= 8
use_fp16 = not use_bf16
print(f"Precision: bf16={use_bf16} | fp16={use_fp16} (sm_{major}x)")
# Quantization backend: 4-bit QLoRA only if bitsandbytes actually imports. It is broken on
# some Kaggle/CUDA images (missing CUDA binary; 'No module named triton.ops'). Otherwise fall
# back to plain LoRA in bf16/fp16 -- a 3.8B model fits a 16GB T4 without 4-bit.
use_4bit = False
if device == "cuda":
try:
import bitsandbytes # noqa: F401
use_4bit = True
print("bitsandbytes available -> using 4-bit QLoRA")
except Exception as e:
print(f"[info] bitsandbytes unavailable ({type(e).__name__}: {e}); "
f"falling back to {'bf16' if use_bf16 else 'fp16'} LoRA (no 4-bit).")
# 2. Paths
dataset_dir = "data/spider"
train_file = os.path.join(dataset_dir, "train.jsonl")
val_file = os.path.join(dataset_dir, "dev.jsonl")
output_dir = "models/phi3-text-to-sql"
adapter_dir = "models/phi3-text-to-sql-adapter"
if not os.path.exists(train_file) or not os.path.exists(val_file):
print("ERROR: Dataset files not found. Run dataset.py first.")
sys.exit(1)
# 3. Model load config: 4-bit quant when available, else full-precision (bf16/fp16) LoRA.
compute_dtype = torch.bfloat16 if use_bf16 else torch.float16
model_id = "microsoft/Phi-3-mini-4k-instruct"
load_kwargs = dict(trust_remote_code=False, attn_implementation="eager")
if use_4bit:
print("Configuring 4-bit Quantization (QLoRA)...")
load_kwargs["quantization_config"] = BitsAndBytesConfig(
load_in_4bit=True,
bnb_4bit_quant_type="nf4",
bnb_4bit_use_double_quant=True,
bnb_4bit_compute_dtype=compute_dtype,
)
load_kwargs["device_map"] = "auto"
from accelerate import Accelerator
load_kwargs["device_map"] = {"": Accelerator().local_process_index}
else:
load_kwargs["torch_dtype"] = compute_dtype
load_kwargs["low_cpu_mem_usage"] = True # no device_map: the Trainer places it on the GPU
# 4. Load Model and Tokenizer
print(f"Loading base model: {model_id} (4bit={use_4bit})...")
model = AutoModelForCausalLM.from_pretrained(model_id, **load_kwargs)
print("Loading tokenizer...")
tokenizer = AutoTokenizer.from_pretrained(model_id, trust_remote_code=False)
# Configure padding tokens for Phi-3 (which uses EOS for padding natively in standard chat SFT)
tokenizer.pad_token = tokenizer.eos_token
tokenizer.padding_side = "right"
# 5. Prepare Model for Peft Training (gradient checkpointing + input grads)
if use_4bit:
print("Preparing model for k-bit training...")
model = prepare_model_for_kbit_training(model, use_gradient_checkpointing=True)
else:
print("Enabling gradient checkpointing for full-precision LoRA...")
model.gradient_checkpointing_enable()
model.enable_input_require_grads()
# 6. Configure LoRA
# Phi-3 fuses q/k/v -> qkv_proj and gate/up -> gate_up_proj; targeting these plus o_proj
# and down_proj adapts all 7 logical projections (the v1 config silently hit only 2).
print("Configuring LoRA parameters...")
peft_config = LoraConfig(
r=16, lora_alpha=32,
target_modules=["qkv_proj", "o_proj", "gate_up_proj", "down_proj"],
lora_dropout=0.05, bias="none", task_type="CAUSAL_LM",
)
model = get_peft_model(model, peft_config)
got = adapted_projection_types(model)
assert got == EXPECTED_TARGETS, (
f"LoRA adapted {got}, expected {EXPECTED_TARGETS}. Phi-3 fuses q/k/v->qkv_proj and "
"gate/up->gate_up_proj; the v1 config silently matched only o_proj+down_proj. "
"Refusing to train a partial adapter.")
model.print_trainable_parameters() # expect ~0.5-0.9% now (was 0.12%)
# 7. Load Datasets
print("Loading formatted training datasets...")
dataset = load_dataset("json", data_files={
"train": train_file,
"validation": val_file
})
# Drop examples longer than MAX_LEN so their SQL labels are never truncated
# (~0.95% of Spider train exceeds 2560 tokens; corrupt labels are worse than fewer rows).
def _fits(ex):
return len(tokenizer.apply_chat_template(ex["messages"], tokenize=True)) <= MAX_LEN
before = len(dataset["train"])
dataset["train"] = dataset["train"].filter(_fits)
dataset["validation"] = dataset["validation"].filter(_fits)
print(f"Length filter (<= {MAX_LEN} tok): kept {len(dataset['train'])}/{before} train "
f"(dropped {before - len(dataset['train'])}); dev now {len(dataset['validation'])}")
# 8. Training Arguments
# Memory-optimized: batch_size=1, gradient_accumulation=8 (eff. batch size 8), paged_adamw optimizer
print("Setting up memory-optimized SFT training arguments...")
training_args = SFTConfig(
output_dir=output_dir,
num_train_epochs=2,
per_device_train_batch_size=2,
per_device_eval_batch_size=1,
gradient_accumulation_steps=4,
group_by_length=True,
learning_rate=2e-4,
weight_decay=0.01,
lr_scheduler_type="cosine",
warmup_ratio=0.05,
logging_steps=1,
eval_strategy="steps",
eval_steps=200,
save_strategy="steps",
save_steps=200,
save_total_limit=2,
optim="paged_adamw_8bit" if use_4bit else "adamw_torch",
bf16=use_bf16,
fp16=use_fp16,
gradient_checkpointing=True,
gradient_checkpointing_kwargs={"use_reentrant": False},
max_grad_norm=0.3,
report_to="none",
ddp_find_unused_parameters=False,
remove_unused_columns=True, # Allow Trainer to drop the raw 'messages' column after tokenization
max_seq_length=2560
)
# 9. Initialize SFTTrainer
# SFTTrainer natively formats 'messages' using the model's chat template
print("Initializing SFTTrainer...")
trainer = SFTTrainer(
model=model,
train_dataset=dataset["train"],
eval_dataset=dataset["validation"],
peft_config=None,
processing_class=tokenizer,
args=training_args,
callbacks=[VRAMLoggerCallback()] if device == "cuda" else []
)
# 10. Execute Fine-Tuning
print("Launching Fine-Tuning loop...")
trainer.train()
# 11. Save the Fine-Tuned Adapter
print(f"Fine-tuning complete! Saving adapter to {adapter_dir}...")
trainer.model.save_pretrained(adapter_dir)
tokenizer.save_pretrained(adapter_dir)
print("Adapter saved successfully. Ready for evaluation and deployment!")
if __name__ == "__main__":
train()
|