| from __future__ import annotations |
|
|
| import math |
| from typing import Any |
|
|
| import torch |
| from peft import LoraConfig, TaskType, get_peft_model, prepare_model_for_kbit_training |
| from transformers import BitsAndBytesConfig, get_cosine_schedule_with_warmup |
|
|
|
|
| def str_to_torch_dtype(name: str | None) -> torch.dtype | None: |
| if name is None: |
| return None |
| value = str(name).lower() |
| if value in {"bfloat16", "bf16"}: |
| return torch.bfloat16 |
| if value in {"float16", "fp16"}: |
| return torch.float16 |
| if value in {"float32", "fp32"}: |
| return torch.float32 |
| raise ValueError(f"Unsupported torch dtype: {name}") |
|
|
|
|
| def quantization_config_from_config(config: dict[str, Any]) -> BitsAndBytesConfig | None: |
| if not config.get("load_in_4bit", False): |
| return None |
| return BitsAndBytesConfig( |
| load_in_4bit=True, |
| bnb_4bit_quant_type=config.get("bnb_4bit_quant_type", "nf4"), |
| bnb_4bit_compute_dtype=str_to_torch_dtype(config.get("bnb_4bit_compute_dtype", "bfloat16")), |
| bnb_4bit_use_double_quant=bool(config.get("bnb_4bit_use_double_quant", True)), |
| ) |
|
|
|
|
| def apply_lora(model: torch.nn.Module, config: dict[str, Any]) -> torch.nn.Module: |
| if config.get("load_in_4bit", False): |
| model = prepare_model_for_kbit_training( |
| model, |
| use_gradient_checkpointing=bool(config.get("gradient_checkpointing", True)), |
| ) |
| lora_config = LoraConfig( |
| r=int(config.get("lora_r", 16)), |
| lora_alpha=int(config.get("lora_alpha", 32)), |
| lora_dropout=float(config.get("lora_dropout", 0.05)), |
| target_modules=list(config.get("target_modules", [])), |
| bias="none", |
| task_type=TaskType.CAUSAL_LM, |
| ) |
| return get_peft_model(model, lora_config) |
|
|
|
|
| def build_optimizer(model: torch.nn.Module, config: dict[str, Any]) -> torch.optim.Optimizer: |
| trainable = [p for p in model.parameters() if p.requires_grad] |
| if not trainable: |
| raise ValueError("No trainable parameters found. Check LoRA target_modules.") |
| return torch.optim.AdamW( |
| trainable, |
| lr=float(config.get("learning_rate", 1e-4)), |
| weight_decay=float(config.get("weight_decay", 0.0)), |
| ) |
|
|
|
|
| def build_scheduler(optimizer: torch.optim.Optimizer, config: dict[str, Any], steps_per_epoch: int): |
| epochs = int(config.get("num_train_epochs", 1)) |
| total_steps = max(1, steps_per_epoch * epochs) |
| warmup_steps = math.ceil(total_steps * float(config.get("warmup_ratio", 0.03))) |
| return get_cosine_schedule_with_warmup(optimizer, warmup_steps, total_steps) |
|
|
|
|
| def oom_help() -> str: |
| return ( |
| "CUDA out of memory. Try reducing max_frames, train_max_samples, or " |
| "per_device_train_batch_size; use a smaller model_name; keep load_in_4bit true; " |
| "or run on a GPU with more VRAM." |
| ) |
|
|