muterornament's picture
Industrialize: Backup sovereign training pipeline
e5b79b7 verified
Raw
History Blame Contribute Delete
6.26 kB
import os
import torch
from transformers import (
AutoModelForCausalLM,
AutoTokenizer,
BitsAndBytesConfig,
TrainingArguments,
Trainer,
DataCollatorForLanguageModeling
)
from peft import (
LoraConfig,
get_peft_model,
prepare_model_for_kbit_training
)
from datasets import load_dataset
# --- CLOSET & OFFLINE SETTINGS ---
os.environ["HF_HUB_OFFLINE"] = "1"
os.environ["WANDB_DISABLED"] = "true"
os.environ["PYTORCH_CUDA_ALLOC_CONF"] = "expandable_segments:True"
def finetune(
model_path: str,
dataset_path: str,
output_dir: str = "./output",
batch_size: int = 1,
gradient_accumulation_steps: int = 4,
learning_rate: float = 2e-4,
epochs: int = 1,
lora_r: int = 8,
lora_alpha: int = 16,
lora_dropout: float = 0.05,
hf_token: str = None
):
if hf_token:
from huggingface_hub import login
login(token=hf_token)
print(f"Loading tokenizer from {model_path}...")
tokenizer = AutoTokenizer.from_pretrained(model_path, trust_remote_code=True)
if tokenizer.pad_token is None:
tokenizer.pad_token = tokenizer.eos_token
print("Loading model in 4-bit (QLoRA) for 8GB VRAM optimization...")
bnb_config = BitsAndBytesConfig(
load_in_4bit=True,
bnb_4bit_use_double_quant=True,
bnb_4bit_quant_type="nf4",
bnb_4bit_compute_dtype=torch.bfloat16
)
model = AutoModelForCausalLM.from_pretrained(
model_path,
quantization_config=bnb_config,
device_map="auto",
trust_remote_code=True
)
# Prepare for kbit training
model.gradient_checkpointing_enable()
model = prepare_model_for_kbit_training(model)
# LoRA Configuration
config = LoraConfig(
r=lora_r,
lora_alpha=lora_alpha,
target_modules=["q_proj", "v_proj", "k_proj", "o_proj", "gate_proj", "up_proj", "down_proj"], # Adjusted for common local architectures
lora_dropout=lora_dropout,
bias="none",
task_type="CAUSAL_LM"
)
model = get_peft_model(model, config)
model.print_trainable_parameters()
print(f"Loading dataset from {dataset_path}...")
# Supporting local json/jsonl datasets OR Hugging Face datasets
if os.path.exists(dataset_path):
dataset = load_dataset("json", data_files=dataset_path, split="train")
else:
print(f"Dataset path '{dataset_path}' not found locally. Attempting to load from Hugging Face...")
dataset = load_dataset(dataset_path, split="train", token=hf_token)
def tokenize_function(examples):
# 1. Handle Chat/Message format (e.g., ultrachat_200k)
if "messages" in examples:
# Format as alternating User/Assistant turns
texts = []
for msg_list in examples["messages"]:
formatted_text = ""
for msg in msg_list:
role = msg['role'].capitalize()
content = msg['content']
formatted_text += f"{role}: {content}\n"
texts.append(formatted_text)
return tokenizer(texts, truncation=True, padding="max_length", max_length=512)
# 2. Handle Orca format (system, question, response)
if "system_prompt" in examples and "question" in examples:
texts = [
f"### System:\n{s}\n\n### Question:\n{q}\n\n### Response:\n{r}"
for s, q, r in zip(examples["system_prompt"], examples["question"], examples["response"])
]
return tokenizer(texts, truncation=True, padding="max_length", max_length=512)
# 3. Handle Alpaca format (instruction, input, output)
if "instruction" in examples and "output" in examples:
texts = []
for i, inp, o in zip(examples["instruction"], examples.get("input", [""] * len(examples["instruction"])), examples["output"]):
text = f"### Instruction:\n{i}"
if inp:
text += f"\n\n### Input:\n{inp}"
text += f"\n\n### Response:\n{o}"
texts.append(text)
return tokenizer(texts, truncation=True, padding="max_length", max_length=512)
# 4. Fallback: Search for generic text fields
text_field = "text"
if "text" not in examples:
for field in ["instruction", "content", "response", "output"]:
if field in examples:
text_field = field
break
return tokenizer(examples[text_field], truncation=True, padding="max_length", max_length=512)
tokenized_dataset = dataset.map(tokenize_function, batched=True, remove_columns=dataset.column_names)
training_args = TrainingArguments(
output_dir=output_dir,
per_device_train_batch_size=batch_size,
gradient_accumulation_steps=gradient_accumulation_steps,
learning_rate=learning_rate,
num_train_epochs=epochs,
logging_steps=10,
save_strategy="steps",
save_steps=100,
evaluation_strategy="no",
fp16=False,
bf16=True, # Recommended for RTX 40 series
optim="paged_adamw_8bit", # Optimize VRAM
report_to="none"
)
trainer = Trainer(
model=model,
args=training_args,
train_dataset=tokenized_dataset,
data_collator=DataCollatorForLanguageModeling(tokenizer, mlm=False),
)
print("Starting training...")
trainer.train()
print(f"Saving final adapter to {output_dir}...")
model.save_pretrained(output_dir)
print("Finetuning complete.")
if __name__ == "__main__":
import argparse
parser = argparse.ArgumentParser(description="8GB VRAM Optimized QLoRA Finetuner")
parser.add_argument("--model", type=str, required=True, help="Local path to the model")
parser.add_argument("--dataset", type=str, required=True, help="Local path or HF ID to .json/.jsonl dataset")
parser.add_argument("--output", type=str, default="./finetuned_model", help="Directory to save output")
parser.add_argument("--hf-token", type=str, help="Hugging Face API token")
args = parser.parse_args()
finetune(args.model, args.dataset, args.output, hf_token=args.hf_token)