Spaces:
Running
Running
| """QLoRA fine-tune of the ScrubData planner β Colab Pro+ (A100) or HF Jobs. | |
| Trains a β€4B model (default Qwen3-4B-Instruct-2507) on our verified SFT data to emit | |
| the JSON cleaning plan reliably + in our conventions, then exports a Q4_K_M GGUF for | |
| llama.cpp and pushes both adapter and GGUF to the Hub. | |
| Recipe per project research (memory: training-recipe): A100/L4 β 16-bit LoRA; | |
| r=32, alpha=32, all 7 target modules; LR 2e-4, 2-3 epochs, bf16. On a small GPU it | |
| auto-falls back to 4-bit QLoRA. | |
| Run (Colab, after the 3 setup cells in notebooks/README.md): | |
| !python notebooks/train_qlora.py \ | |
| --data-repo build-small-hackathon/scrubdata-sft \ | |
| --out-repo <your-user>/scrubdata-qwen3-4b | |
| HF_TOKEN must be set in the environment (Colab Secrets / `os.environ`). | |
| """ | |
| from __future__ import annotations | |
| import argparse | |
| import os | |
| def main() -> None: | |
| ap = argparse.ArgumentParser() | |
| ap.add_argument("--base", default="unsloth/Qwen3-4B-Instruct-2507") | |
| ap.add_argument("--data-repo", default="build-small-hackathon/scrubdata-sft", | |
| help="HF dataset repo holding train.jsonl (messages format)") | |
| ap.add_argument("--data-file", default="train.jsonl") | |
| ap.add_argument("--out-repo", default=None, help="HF repo to push adapter + GGUF") | |
| ap.add_argument("--epochs", type=float, default=2.0) | |
| ap.add_argument("--max-seq", type=int, default=8192, | |
| help="v3 examples reach ~5.5k tokens; keep β₯6144 to avoid truncation") | |
| args = ap.parse_args() | |
| import torch | |
| from unsloth import FastLanguageModel | |
| from datasets import load_dataset | |
| from trl import SFTConfig, SFTTrainer | |
| hf_token = os.environ.get("HF_TOKEN") | |
| # Big GPU (β24GB+) β 16-bit LoRA (quality edge); small GPU β 4-bit QLoRA. | |
| vram = torch.cuda.get_device_properties(0).total_memory if torch.cuda.is_available() else 0 | |
| load_in_4bit = vram < 22e9 | |
| big = not load_in_4bit | |
| print(f"GPU VRAM={vram/1e9:.0f}GB β {'16-bit LoRA' if big else '4-bit QLoRA'}") | |
| model, tokenizer = FastLanguageModel.from_pretrained( | |
| model_name=args.base, max_seq_length=args.max_seq, | |
| load_in_4bit=load_in_4bit, full_finetuning=False) | |
| model = FastLanguageModel.get_peft_model( | |
| model, r=32, lora_alpha=32, lora_dropout=0, bias="none", | |
| target_modules=["q_proj", "k_proj", "v_proj", "o_proj", | |
| "gate_proj", "up_proj", "down_proj"], | |
| use_gradient_checkpointing="unsloth", random_state=0) | |
| ds = load_dataset(args.data_repo, data_files=args.data_file, split="train") | |
| def fmt(ex): | |
| return {"text": tokenizer.apply_chat_template( | |
| ex["messages"], tokenize=False, add_generation_prompt=False)} | |
| ds = ds.map(fmt, remove_columns=ds.column_names) | |
| trainer = SFTTrainer( | |
| model=model, tokenizer=tokenizer, train_dataset=ds, | |
| args=SFTConfig( | |
| dataset_text_field="text", max_seq_length=args.max_seq, | |
| # smaller batch since sequences are long now (~6k); effective batch stays 16 | |
| per_device_train_batch_size=4 if big else 1, | |
| gradient_accumulation_steps=4 if big else 16, | |
| warmup_steps=5, num_train_epochs=args.epochs, learning_rate=2e-4, | |
| logging_steps=10, optim="adamw_8bit", weight_decay=0.001, | |
| lr_scheduler_type="linear", seed=0, bf16=big, fp16=not big, | |
| output_dir="outputs", report_to="none")) | |
| # Train only on the assistant's plan (mask the prompt) for cleaner SFT. | |
| try: | |
| from unsloth.chat_templates import train_on_responses_only | |
| trainer = train_on_responses_only( | |
| trainer, | |
| instruction_part="<|im_start|>user\n", | |
| response_part="<|im_start|>assistant\n") | |
| except Exception as e: # template markers vary by base model β non-fatal | |
| print(f"(train_on_responses_only skipped: {e})") | |
| trainer.train() | |
| out = "scrubdata-qwen3-4b" | |
| model.save_pretrained_gguf(out, tokenizer, quantization_method="q8_0") | |
| print(f"Saved GGUF under ./{out}") | |
| if args.out_repo and hf_token: | |
| model.push_to_hub(args.out_repo, token=hf_token) | |
| tokenizer.push_to_hub(args.out_repo, token=hf_token) | |
| model.push_to_hub_gguf(f"{args.out_repo}-gguf", tokenizer, | |
| quantization_method="q8_0", token=hf_token) | |
| print(f"Pushed adapter β {args.out_repo} and GGUF β {args.out_repo}-gguf") | |
| else: | |
| print("Set --out-repo and HF_TOKEN to push artifacts to the Hub.") | |
| if __name__ == "__main__": | |
| main() | |