frohzinn/bcbsma-storage / train_lora.py
frohzinn's picture
download
raw
3.62 kB
#!/usr/bin/env python3
"""QLoRA fine-tune of Gemma 2 9B-it as a malevolent/benign classifier.
Run on a CUDA GPU (~24 GB is comfortable for 9B QLoRA). Data is the HF chat-format file
produced by ../ft_dataset/build_ft_jsonl.py (Format B: instruction folded into the user turn,
which suits Gemma 2 since it has no system role).
huggingface-cli login # and accept the Gemma license on the model page
python train_lora.py
"""
import torch
from datasets import load_dataset
from transformers import AutoTokenizer, AutoModelForCausalLM, BitsAndBytesConfig
from peft import LoraConfig
from trl import SFTTrainer, SFTConfig
from data_utils import to_messages # shared with eval_testset.py (single source of truth)
MODEL_ID = "google/gemma-2-9b-it" # text-only, plain CausalLM, mature GGUF/Ollama support.
# (Gemma 4 12B: GGUF conversion not ready. Gemma 3 12B: multimodal, fights AutoModelForCausalLM.
# Gemma 2 9B avoids both and is plenty for a binary classifier. Swap to Qwen2.5-14B-Instruct if you prefer.)
# Reuse the already-generated, committed datasets (produced once by ../ft_dataset/build_ft_jsonl.py).
# This script only CONSUMES these files — it does not generate or regenerate any data.
TRAIN_FILE = "bcbsma_injection_ft_train.jsonl" # 800 examples (400/400)
TEST_FILE = "bcbsma_injection_ft_test.jsonl" # 100/100 held-out (also used by eval_testset.py)
OUT_DIR = "out"
def main():
# Reuse both committed files: train on TRAIN_FILE, monitor eval loss on the held-out TEST_FILE.
ds = load_dataset("json", data_files={"train": TRAIN_FILE, "test": TEST_FILE}) # rows have {"messages": [...]}
tok = AutoTokenizer.from_pretrained(MODEL_ID)
# Normalize either dataset format (HF "messages" or Vertex "contents") to one "text"
# column via the chat template. to_messages() is shared with eval_testset.py (data_utils.py).
def to_text(ex):
return {"text": tok.apply_chat_template(to_messages(ex), tokenize=False)}
ds = ds.map(to_text, remove_columns=ds["train"].column_names)
bnb = BitsAndBytesConfig(
load_in_4bit=True, bnb_4bit_quant_type="nf4",
bnb_4bit_compute_dtype=torch.bfloat16, bnb_4bit_use_double_quant=True,
)
model = AutoModelForCausalLM.from_pretrained(
MODEL_ID, quantization_config=bnb, torch_dtype=torch.bfloat16,
device_map="auto", attn_implementation="eager", # eager is safest for Gemma 2 soft-capping
)
lora = LoraConfig(
r=16, lora_alpha=32, lora_dropout=0.05, bias="none", task_type="CAUSAL_LM",
target_modules=["q_proj", "k_proj", "v_proj", "o_proj", "gate_proj", "up_proj", "down_proj"],
)
cfg = SFTConfig(
output_dir=OUT_DIR, num_train_epochs=3,
per_device_train_batch_size=2, gradient_accumulation_steps=8, # small batch + accum; 9B QLoRA fits ~16GB+
learning_rate=2e-4, lr_scheduler_type="cosine", warmup_ratio=0.03,
logging_steps=10, save_strategy="epoch", bf16=True,
eval_strategy="epoch", per_device_eval_batch_size=4, # eval loss on the held-out test set each epoch
max_length=512, packing=False, report_to="none", # SFTConfig renamed max_seq_length -> max_length
dataset_text_field="text", # we pre-rendered the chat into a "text" column above
)
trainer = SFTTrainer(model=model, train_dataset=ds["train"], eval_dataset=ds["test"],
peft_config=lora, args=cfg, processing_class=tok)
trainer.train()
trainer.save_model(f"{OUT_DIR}/adapter")
print(f"Saved LoRA adapter to {OUT_DIR}/adapter")
if __name__ == "__main__":
main()

Xet Storage Details

Size:
3.62 kB
·
Xet hash:
8fe2299cef84493d403182be9a1428eba5e0766870f2297a45be04004d80cdcb

Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.