Documentation hub — YouTube

Blog · Sep 5, 2025

How to finetune your local llm using LORA

Train in hours not days using LORA

LoRA = Tiny Add-On Brains 🧠

Instead of training all billion parameters, LoRA adds small trainable matrices.

Think of it as teaching just a few neurons, not rewiring the whole brain.

Magic Settings ⚡

  • Rank (r): How many “extra neurons” LoRA adds (small r = more compression).

  • Alpha: How strong those neurons influence the model.

  • Dropout: Prevents overfitting — like making the neurons forget some details.

Result = Cheap + Fast Training 💸

  • Train a model on your laptop in hours (not days).

  • Save GPU memory while keeping accuracy.

  • Perfect for fine-tuning chatbots, code assistants, or small domain models.

Click to expand and see dependancy for this project📌 ``` name: lora channels: - defaults dependencies: - _libgcc_mutex=0.1=main - _openmp_mutex=5.1=1_gnu - bzip2=1.0.8=h5eee18b_6 - ca-certificates=2025.7.15=h06a4308_0 - expat=2.7.1=h6a678d5_0 - ld_impl_linux-64=2.40=h12ee557_0 - libffi=3.4.4=h6a678d5_1 - libgcc-ng=11.2.0=h1234567_1 - libgomp=11.2.0=h1234567_1 - libstdcxx-ng=11.2.0=h1234567_1 - libuuid=1.41.5=h5eee18b_0 - libxcb=1.17.0=h9b100fa_0 - ncurses=6.5=h7934f7d_0 - openssl=3.0.17=h5eee18b_0 - pip=25.2=pyhc872135_0 - pthread-stubs=0.3=h0ce48e5_1 - python=3.10.18=h1a3bd86_0 - readline=8.3=hc2a1206_0 - setuptools=78.1.1=py310h06a4308_0 - sqlite=3.50.2=hb25bd0a_1 - tk=8.6.15=h54e0aa7_0 - wheel=0.45.1=py310h06a4308_0 - xorg-libx11=1.8.12=h9b100fa_1 - xorg-libxau=1.0.12=h9b100fa_0 - xorg-libxdmcp=1.1.5=h9b100fa_0 - xorg-xorgproto=2024.1=h5eee18b_1 - xz=5.6.4=h5eee18b_1 - zlib=1.2.13=h5eee18b_1 - pip: - accelerate==0.31.0 - aiohappyeyeballs==2.6.1 - aiohttp==3.12.15 - aiosignal==1.4.0 - alembic==1.16.5 - annotated-types==0.7.0 - annoy==1.17.3 - anyio==4.10.0 - argon2-cffi==25.1.0 - argon2-cffi-bindings==25.1.0 - arrow==1.3.0 - asttokens==3.0.0 - async-lru==2.0.5 - async-timeout==4.0.3 - attrs==25.3.0 - babel==2.17.0 - banal==1.0.6 - beautifulsoup4==4.13.5 - bertopic==0.16.3 - bitsandbytes==0.43.1 - bleach==6.2.0 - boto3==1.40.24 - botocore==1.40.24 - certifi==2025.8.3 - cffi==1.17.1 - charset-normalizer==3.4.3 - click==8.2.1 - cohere==5.5.8 - colorcet==3.1.0 - colorspacious==1.1.2 - comm==0.2.3 - contourpy==1.3.2 - cycler==0.12.1 - dataclasses-json==0.6.7 - datamapplot==0.3.0 - dataset==1.6.2 - datasets==2.20.0 - datashader==0.18.2 - debugpy==1.8.16 - decorator==5.2.1 - defusedxml==0.7.1 - dill==0.3.8 - diskcache==5.6.3 - distro==1.9.0 - docstring-parser==0.17.0 - duckduckgo-search==7.1.1 - eval-type-backport==0.2.2 - evaluate==0.4.2 - exceptiongroup==1.3.0 - executing==2.2.1 - faiss-cpu==1.8.0 - fastavro==1.12.0 - fastjsonschema==2.21.2 - filelock==3.19.1 - fonttools==4.59.2 - fqdn==1.5.1 - frozenlist==1.7.0 - fsspec==2024.5.0 - gensim==4.3.2 - greenlet==3.2.4 - h11==0.16.0 - hdbscan==0.8.40 - hf-xet==1.1.9 - httpcore==1.0.9 - httpx==0.28.1 - httpx-sse==0.4.1 - huggingface-hub==0.34.4 - idna==3.10 - imageio==2.37.0 - ipykernel==6.30.1 - ipython==8.37.0 - ipywidgets==8.1.3 - isoduration==20.11.0 - jedi==0.19.2 - jinja2==3.1.6 - jmespath==1.0.1 - joblib==1.5.2 - json5==0.12.1 - jsonpatch==1.33 - jsonpointer==3.0.0 - jsonschema==4.25.1 - jsonschema-specifications==2025.4.1 - jupyter-client==8.6.3 - jupyter-core==5.8.1 - jupyter-events==0.12.0 - jupyter-lsp==2.3.0 - jupyter-server==2.17.0 - jupyter-server-terminals==0.5.3 - jupyterlab==4.2.2 - jupyterlab-pygments==0.3.0 - jupyterlab-server==2.27.3 - jupyterlab-widgets==3.0.15 - kiwisolver==1.4.9 - langchain==0.2.5 - langchain-community==0.2.5 - langchain-core==0.2.43 - langchain-openai==0.1.8 - langchain-text-splitters==0.2.4 - langsmith==0.1.147 - lark==1.2.2 - lazy-loader==0.4 - llama-cpp-python==0.2.78 - llvmlite==0.44.0 - lxml==6.0.1 - mako==1.3.10 - markdown-it-py==4.0.0 - markupsafe==3.0.2 - marshmallow==3.26.1 - matplotlib==3.9.0 - matplotlib-inline==0.1.7 - mdurl==0.1.2 - mistune==3.1.4 - mpmath==1.3.0 - mteb==1.12.39 - multidict==6.6.4 - multipledispatch==1.0.0 - multiprocess==0.70.16 - mypy-extensions==1.1.0 - narwhals==2.3.0 - nbclient==0.10.2 - nbconvert==7.16.6 - nbformat==5.10.4 - nest-asyncio==1.6.0 - networkx==3.4.2 - nltk==3.8.1 - notebook-shim==0.2.4 - numba==0.61.2 - numexpr==2.10.0 - numpy==1.26.4 - nvidia-cublas-cu12==12.1.3.1 - nvidia-cuda-cupti-cu12==12.1.105 - nvidia-cuda-nvrtc-cu12==12.1.105 - nvidia-cuda-runtime-cu12==12.1.105 - nvidia-cudnn-cu12==8.9.2.26 - nvidia-cufft-cu12==11.0.2.54 - nvidia-cufile-cu12==1.13.1.3 - nvidia-curand-cu12==10.3.2.106 - nvidia-cusolver-cu12==11.4.5.107 - nvidia-cusparse-cu12==12.1.0.106 - nvidia-cusparselt-cu12==0.7.1 - nvidia-nccl-cu12==2.20.5 - nvidia-nvjitlink-cu12==12.8.93 - nvidia-nvtx-cu12==12.1.105 - openai==1.34.0 - orjson==3.11.3 - overrides==7.7.0 - packaging==24.2 - pandas==2.2.2 - pandocfilters==1.5.1 - param==2.2.1 - parameterized==0.9.0 - parso==0.8.5 - peft==0.11.1 - pexpect==4.9.0 - pillow==11.3.0 - platformdirs==4.4.0 - plotly==6.3.0 - polars==1.33.0 - primp==0.15.0 - prometheus-client==0.22.1 - prompt-toolkit==3.0.52 - propcache==0.3.2 - psutil==7.0.0 - ptyprocess==0.7.0 - pure-eval==0.2.3 - pyarrow==21.0.0 - pyarrow-hotfix==0.7 - pycparser==2.22 - pyct==0.5.0 - pydantic==2.11.7 - pydantic-core==2.33.2 - pygments==2.19.2 - pylabeladjust==0.1.13 - pynndescent==0.5.13 - pyparsing==3.2.3 - pyqtree==1.0.0 - python-dateutil==2.9.0.post0 - python-json-logger==3.3.0 - pytrec-eval-terrier==0.5.8 - pytz==2025.2 - pyyaml==6.0.2 - pyzmq==27.0.2 - referencing==0.36.2 - regex==2025.9.1 - requests==2.32.5 - requests-toolbelt==1.0.0 - rfc3339-validator==0.1.4 - rfc3986-validator==0.1.1 - rfc3987-syntax==1.1.0 - rich==14.1.0 - rpds-py==0.27.1 - s3transfer==0.13.1 - safetensors==0.6.2 - scikit-image==0.25.2 - scikit-learn==1.5.0 - scipy==1.15.3 - send2trash==1.8.3 - sentence-transformers==3.0.1 - sentencepiece==0.2.0 - seqeval==1.2.2 - setfit==1.0.3 - shtab==1.7.2 - six==1.17.0 - smart-open==7.3.0.post1 - sniffio==1.3.1 - soupsieve==2.8 - sqlalchemy==1.4.54 - stack-data==0.6.3 - sympy==1.14.0 - tenacity==8.5.0 - terminado==0.18.1 - threadpoolctl==3.6.0 - tifffile==2025.5.10 - tiktoken==0.11.0 - tinycss2==1.4.0 - tokenizers==0.19.1 - tomli==2.2.1 - toolz==1.0.0 - torch==2.3.1 - tornado==6.5.2 - tqdm==4.67.1 - traitlets==5.14.3 - transformers==4.41.2 - triton==2.3.1 - trl==0.9.4 - typeguard==4.4.4 - types-python-dateutil==2.9.0.20250822 - types-requests==2.32.4.20250809 - typing-extensions==4.15.0 - typing-inspect==0.9.0 - typing-inspection==0.4.1 - tyro==0.9.31 - tzdata==2025.2 - umap-learn==0.5.7 - uri-template==1.3.0 - urllib3==2.5.0 - wcwidth==0.2.13 - webcolors==24.11.1 - webencodings==0.5.1 - websocket-client==1.8.0 - widgetsnbextension==4.0.14 - wrapt==1.17.3 - xarray==2025.6.1 - xxhash==3.5.0 - yarl==1.20.1 - zstandard==0.24.0 prefix: /home/sv/anaconda3/envs/lora ```

see code for quickly train model using LORA


# -*- coding: utf-8 -*-
# Install the requirements in Google Colab
# !pip install transformers datasets trl huggingface_hub

# Authenticate to Hugging Face

# from huggingface_hub import login
#
# login()

# for convenience you can create an environment variable containing your hub token as HF_TOKEN

"""## 2. Load the dataset"""

# Load a sample dataset
from datasets import load_dataset

# TODO: define your dataset and config using the path and name parameters
dataset = load_dataset(path="HuggingFaceTB/smoltalk", name="everyday-conversations")

# 2. Show first row
print("data sample")
print(dataset['test'][0])

# Import necessary libraries
from transformers import AutoModelForCausalLM, AutoTokenizer
from datasets import load_dataset
from trl import SFTConfig, SFTTrainer, setup_chat_format
import torch

device = (
    "cuda"
    if torch.cuda.is_available()
    else "mps" if torch.backends.mps.is_available() else "cpu"
)

# Load the model and tokenizer
model_name = "HuggingFaceTB/SmolLM2-135M"

model = AutoModelForCausalLM.from_pretrained(
    pretrained_model_name_or_path=model_name
).to(device)
tokenizer = AutoTokenizer.from_pretrained(pretrained_model_name_or_path=model_name)

# Set up the chat format
model, tokenizer = setup_chat_format(model=model, tokenizer=tokenizer)

# Set our name for the finetune to be saved &/ uploaded to
finetune_name = "SmolLM2-FT-MyDataset"
finetune_tags = ["smol-course", "module_1"]


from peft import LoraConfig

# TODO: Configure LoRA parameters
# r: rank dimension for LoRA update matrices (smaller = more compression)
rank_dimension = 6
# lora_alpha: scaling factor for LoRA layers (higher = stronger adaptation)
lora_alpha = 8
# lora_dropout: dropout probability for LoRA layers (helps prevent overfitting)
lora_dropout = 0.05

peft_config = LoraConfig(
    r=rank_dimension,  # Rank dimension - typically between 4-32
    lora_alpha=lora_alpha,  # LoRA scaling factor - typically 2x rank
    lora_dropout=lora_dropout,  # Dropout probability for LoRA layers
    bias="none",  # Bias type for LoRA. the corresponding biases will be updated during training.
    target_modules="all-linear",  # Which modules to apply LoRA to
    task_type="CAUSAL_LM",  # Task type for model architecture
)

"""Before we can start our training we need to define the hyperparameters (`TrainingArguments`) we want to use."""

# Training configuration
# Hyperparameters based on QLoRA paper recommendations
args = SFTConfig(
    # Output settings
    output_dir=finetune_name,  # Directory to save model checkpoints
    # Training duration
    num_train_epochs=1,  # Number of training epochs
    # Batch size settings
    per_device_train_batch_size=2,  # Batch size per GPU
    gradient_accumulation_steps=2,  # Accumulate gradients for larger effective batch
    # Memory optimization
    gradient_checkpointing=True,  # Trade compute for memory savings
    # Optimizer settings
    optim="adamw_torch_fused",  # Use fused AdamW for efficiency
    learning_rate=2e-4,  # Learning rate (QLoRA paper)
    max_grad_norm=0.3,  # Gradient clipping threshold
    # Learning rate schedule
    warmup_ratio=0.03,  # Portion of steps for warmup
    lr_scheduler_type="constant",  # Keep learning rate constant after warmup
    # Logging and saving
    logging_steps=10,  # Log metrics every N steps
    save_strategy="epoch",  # Save checkpoint every epoch
    # Precision settings
    bf16=True,  # Use bfloat16 precision
    # Integration settings
    push_to_hub=False,  # Don't push to HuggingFace Hub
    report_to="none",  # Disable external logging
)


max_seq_length = 1512  # max sequence length for model and packing of the dataset

# Create SFTTrainer with LoRA configuration
trainer = SFTTrainer(
    model=model,
    args=args,
    train_dataset=dataset["train"],
    peft_config=peft_config,  # LoRA configuration
    max_seq_length=max_seq_length,  # Maximum sequence length
    tokenizer=tokenizer,
    packing=True,  # Enable input packing for efficiency
    dataset_kwargs={
        "add_special_tokens": False,  # Special tokens handled by template
        "append_concat_token": False,  # No additional separator needed
    },
)

"""Start training our model by calling the `train()` method on our `Trainer` instance. This will start the training loop and train our model for 3 epochs. Since we are using a PEFT method, we will only save the adapted model weights and not the full model."""

# start training, the model will be automatically saved to the hub and the output directory
trainer.train()

# save model
trainer.save_model()


from peft import AutoPeftModelForCausalLM


# Load PEFT model on CPU
model = AutoPeftModelForCausalLM.from_pretrained(
    pretrained_model_name_or_path=args.output_dir,
    torch_dtype=torch.float16,
    low_cpu_mem_usage=True,
)

# Merge LoRA and base model and save
merged_model = model.merge_and_unload()
merged_model.save_pretrained(
    args.output_dir, safe_serialization=True, max_shard_size="2GB"
)

# free the memory again
del model
del trainer
torch.cuda.empty_cache()

How to use this trained model

from transformers import pipeline, AutoTokenizer
from peft import AutoPeftModelForCausalLM
import torch

finetune_name = "./SmolLM2-FT-MyDataset"

# 1. Load tokenizer
tokenizer = AutoTokenizer.from_pretrained(finetune_name)

# 2. Load PEFT (LoRA) model
model = AutoPeftModelForCausalLM.from_pretrained(
    finetune_name,
    device_map="auto",
    torch_dtype=torch.float16
)

# Optional: merge LoRA weights into base
# model = model.merge_and_unload()

# 3. Create inference pipeline
pipe = pipeline(
    "text-generation",
    model=model,
    tokenizer=tokenizer,
    torch_dtype=torch.float16,
    device_map="auto"
)

# ---- Test inference ----
prompt = ("What is the difference between a fruit and a "
          "vegetable? Give examples of each.")
outputs = pipe(prompt, max_new_tokens=100, do_sample=True, temperature=0.7)
print(outputs[0]["generated_text"])


</details>