# ============================================================ # AYURVEDA GEMMA-3 4B TRAINING — MULTI-CELL NOTEBOOK SCRIPT # For: A100 MIG 20GB | Linux | Unsloth # Restart kernel before running Cell 1 # ============================================================ # ============================================================ # CELL 1: Environment & Config # ============================================================ import os import torch # --- CRITICAL: Set cache BEFORE any unsloth/transformers import --- CACHE = "/nlsasfs/home/aikosh/prod-aikosh35/.cache/unsloth" os.makedirs(CACHE, exist_ok=True) os.makedirs(f"{CACHE}/torch", exist_ok=True) os.makedirs(f"{CACHE}/triton", exist_ok=True) os.environ["UNSLOTH_COMPILED_CACHE"] = CACHE os.environ["UNSLOTH_CACHE_DIR"] = CACHE os.environ["TORCHINDUCTOR_CACHE_DIR"] = f"{CACHE}/torch" os.environ["TRITON_CACHE_DIR"] = f"{CACHE}/triton" os.environ["XDG_CACHE_HOME"] = CACHE os.environ["PYTORCH_CUDA_ALLOC_CONF"] = "expandable_segments:True" # --- CONFIG --- MODEL_NAME = "unsloth/gemma-3-4b-it" # 4B fits safely in 20GB MIG DATASET_PATH = "/nlsasfs/home/aikosh/prod-aikosh35/gemma4_ayurveda_unsloth_clean_WITH_REFS.jsonl" OUTPUT_DIR = "/nlsasfs/home/aikosh/prod-aikosh35/gemma3_4b_ayurveda_output" MAX_SEQ_LENGTH = 1024 BATCH_SIZE = 1 GRAD_ACCUM = 32 LORA_R = 16 LORA_ALPHA = 32 LR = 2e-4 EPOCHS = 1 assert torch.cuda.is_available(), "No GPU found!" print(f"GPU: {torch.cuda.get_device_name(0)}") print(f"VRAM: {torch.cuda.get_device_properties(0).total_memory / 1024**3:.1f} GB") print(f"Cache: {CACHE}") print(f"Model: {MODEL_NAME}") print("Config ready. Run Cell 2.") # ============================================================ # CELL 2: Load Dataset # ============================================================ from datasets import load_dataset import gc dataset = load_dataset("json", data_files=DATASET_PATH, split="train") print(f"Loaded {len(dataset):,} conversations") sample = dataset[0] msgs = sample.get("messages", []) print(f"Roles: {[m['role'] for m in msgs]}") print(f"User preview: {msgs[1]['content'][:80]}...") gc.collect() print("Dataset ready. Run Cell 3.") # ============================================================ # CELL 3: Load Model + Tokenizer # ============================================================ from unsloth import FastLanguageModel import gc print(f"Loading {MODEL_NAME}...") model, tokenizer = FastLanguageModel.from_pretrained( model_name=MODEL_NAME, max_seq_length=MAX_SEQ_LENGTH, dtype=None, load_in_4bit=True, trust_remote_code=True, text_only=True, # CRITICAL: skips vision components, saves ~2GB VRAM ) print(f"Model loaded. VRAM: {torch.cuda.memory_allocated() / 1024**3:.1f} GB") print(f"Chat template: {tokenizer.chat_template is not None}") gc.collect() torch.cuda.empty_cache() print("Model ready. Run Cell 4.") # ============================================================ # CELL 4: Format Dataset with Chat Template # ============================================================ # Convert messages -> text string using Gemma chat template def format_chat_template(examples): texts = [] for messages in examples["messages"]: text = tokenizer.apply_chat_template( messages, tokenize=False, add_generation_prompt=False # False for training data ) texts.append(text) return {"text": texts} dataset = dataset.map(format_chat_template, batched=True, remove_columns=["messages"]) print(f"Formatted dataset: {len(dataset):,}") print(f"Sample text length: {len(dataset[0]['text'])} chars") print(f"Sample preview:\n{dataset[0]['text'][:500]}...") gc.collect() print("Dataset formatted. Run Cell 5.") # ============================================================ # CELL 5: Apply LoRA Adapter # ============================================================ from unsloth import FastLanguageModel from peft import PeftModel import gc # Guard: prevent double-LoRA error if cell is re-run if isinstance(model, PeftModel): print("LoRA already attached. Skipping.") else: print("Attaching LoRA adapter...") model = FastLanguageModel.get_peft_model( model, r=LORA_R, lora_alpha=LORA_ALPHA, lora_dropout=0, bias="none", use_gradient_checkpointing="unsloth", random_state=3407, target_modules=[ "q_proj", "k_proj", "v_proj", "o_proj", "gate_proj", "up_proj", "down_proj", ], ) model.print_trainable_parameters() model.config.use_cache = False gc.collect() torch.cuda.empty_cache() print(f"VRAM after LoRA: {torch.cuda.memory_allocated() / 1024**3:.1f} GB") print("LoRA ready. Run Cell 6.") # ============================================================ # CELL 6: Training Config # ============================================================ from trl import SFTConfig total_steps = len(dataset) // (BATCH_SIZE * GRAD_ACCUM) * EPOCHS print(f"Estimated steps: {total_steps:,}") sft_args = SFTConfig( output_dir=OUTPUT_DIR, overwrite_output_dir=True, num_train_epochs=EPOCHS, per_device_train_batch_size=BATCH_SIZE, gradient_accumulation_steps=GRAD_ACCUM, learning_rate=LR, lr_scheduler_type="cosine", warmup_steps=100, optim="adamw_8bit", weight_decay=0.01, max_grad_norm=0.3, bf16=True, fp16=False, logging_steps=25, save_steps=500, save_total_limit=2, report_to="none", dataset_text_field="text", max_length=MAX_SEQ_LENGTH, seed=3407, ) print("SFTConfig ready. Run Cell 7.") # ============================================================ # CELL 7: Initialize Trainer # ============================================================ from trl import SFTTrainer import gc trainer = SFTTrainer( model=model, train_dataset=dataset, processing_class=tokenizer, args=sft_args, ) print("Trainer initialized.") print(f"VRAM before training: {torch.cuda.memory_allocated() / 1024**3:.1f} GB") print("Run Cell 8 to start training.") # ============================================================ # CELL 8: Start Training # ============================================================ import gc gc.collect() torch.cuda.empty_cache() print("=" * 60) print("STARTING GEMMA-3 4B LoRA TRAINING") print("=" * 60) print(f"Dataset : {len(dataset):,} conversations") print(f"Epochs : {EPOCHS}") print(f"Batch : {BATCH_SIZE} (accum: {GRAD_ACCUM}, effective: {BATCH_SIZE * GRAD_ACCUM})") print(f"Seq Length : {MAX_SEQ_LENGTH}") print(f"LoRA : r={LORA_R}, alpha={LORA_ALPHA}") print(f"LR : {LR}") print(f"VRAM : {torch.cuda.memory_allocated() / 1024**3:.1f} GB") print("=" * 60) trainer.train() print("=" * 60) print("TRAINING COMPLETE") print("=" * 60) print("Run Cell 9 to save the adapter.") # ============================================================ # CELL 9: Save Adapter # ============================================================ import os os.makedirs(OUTPUT_DIR, exist_ok=True) adapter_path = os.path.join(OUTPUT_DIR, "adapter") model.save_pretrained(adapter_path) tokenizer.save_pretrained(adapter_path) print(f"Adapter saved to: {adapter_path}") print("Run Cell 10 for inference test.") # ============================================================ # CELL 10: Quick Inference Test # ============================================================ from unsloth import FastLanguageModel FastLanguageModel.for_inference(model) messages = [ {"role": "user", "content": "What does Charaka say about the qualities of a good physician in Sutrasthana?"} ] inputs = tokenizer.apply_chat_template( messages, tokenize=True, return_tensors="pt", add_generation_prompt=True, # True for inference ).to("cuda") print("Generating...") outputs = model.generate( inputs, max_new_tokens=512, temperature=0.7, top_p=0.9, do_sample=True, ) response = tokenizer.decode(outputs[0], skip_special_tokens=True) print("\n" + "=" * 60) print("RESPONSE") print("=" * 60) print(response) print("=" * 60)