π¨ Fine-Tuning LLMs: A Visual GuideΒΆ
A beginner-friendly tour of what fine-tuning is, why PEFT exists, and every parameter strategy shipped in slick-tune: Full FT, LoRA, DoRA, AdaLoRA, and QLoRA β plus multi-adapter merge (TIES / DARE) after training.
No prior ML background required β diagrams first, math second, then how it maps to slick-tune. π
π Table of contentsΒΆ
1. πΊοΈ The big pictureΒΆ
A large language model (LLM) is a giant function: text in β next-token probabilities out. Those probabilities come from billions of numbers called weights (or parameters).
flowchart LR
subgraph world [How models get useful]
PT[Pre-training<br/>learn language from the internet]
FT[Fine-tuning<br/>specialize on your data]
USE[Inference<br/>answer users]
end
PT --> FT --> USE
Fine-tuning means: take an already-trained model and continue training on a smaller, task-specific dataset so it behaves the way you want β for example, answering questions about your product, your company, or (in slick-tuneβs demo) Amirhessam / SlickML.
slick-tune treats fine-tuning as five orthogonal axes:
model Γ strategy Γ objective Γ data Γ metrics
Axis |
Question it answers |
|---|---|
π§ model |
Which base checkpoint? ( |
π§© strategy |
How do weights change? (LoRA, QLoRA, full, β¦) |
π objective |
What loss / data contract? (SFT, DPO, ORPO, KTO, GRPO) |
π data |
Which examples? (train JSONL + optional holdout / prefs / rewards) |
π metrics |
Did it learn? (loss, PPL, probes, judges) |
After training you can also merge adapters (TIES / DARE) or bake them for serving β see Β§14.
You can swap one axis without rewriting the others β that is the whole point of the library.
2. π₯ Pre-training vs prompting vs fine-tuningΒΆ
flowchart TB
subgraph pt [Pre-training]
Huge[Huge public text]
Base[Base / instruct model]
Huge --> Base
end
subgraph prompt [Prompting only]
Base2[Frozen model]
Prompt[Your prompt / RAG]
Out1[Answer]
Base2 --> Prompt --> Out1
end
subgraph ft [Fine-tuning]
Base3[Starting checkpoint]
Data[Your labeled examples]
New[Adapted model / adapter]
Out2[Answer that knows your facts]
Base3 --> Data --> New --> Out2
end
pt --> prompt
pt --> ft
Approach |
Weights change? |
Good for |
Limit |
|---|---|---|---|
Prompting / RAG |
No |
Quick demos, docs lookup |
Context length; model may still hallucinate your facts |
Fine-tuning |
Yes (all or adapters) |
Teaching stable facts/style/format |
Needs data + compute; can overfit |
Pre-training |
Yes, from scratch / continued |
New domains at web scale |
Extremely expensive |
π Rule of thumb: if you need the model to reliably know something small and personal (names, emails, product APIs), fine-tuning + probes beats hoping the prompt sticks.
3. βοΈ What actually changes when you fine-tune?ΒΆ
Inside a Transformer, most compute is linear layers: matrices that mix features.
flowchart LR
x[Input vector x] --> W["Weight matrix W"]
W --> y[Output y = Wx]
During fine-tuning we compute a loss (how wrong the next-token predictions were), then backpropagation produces a gradient for each trainable weight: βnudge this number up or down.β
An optimizer (usually AdamW) applies those nudges for many steps. After enough steps, the modelβs distribution shifts toward your data.
sequenceDiagram
participant D as Your dataset
participant M as Model
participant L as Loss
participant O as Optimizer
D->>M: batch of tokens
M->>L: predictions vs labels
L->>M: gradients
M->>O: trainable params + grads
O->>M: updated weights / adapters
4. π‘ Why not always update every weight?ΒΆ
Updating all weights (full fine-tuning) works, but:
Needs a lot of GPU memory (weights + gradients + optimizer states β severalΓ model size).
Produces a full copy of the model per run (hard to share many variants).
Easy to catastrophically forget general skills if data is tiny.
Parameter-Efficient Fine-Tuning (PEFT) freezes the base model and trains a small add-on (adapter). The most popular adapter family is LoRA.
flowchart TB
subgraph full [Full fine-tuning]
W1[All weights trainable]
end
subgraph peft [PEFT e.g. LoRA]
WF[Base weights frozen]
A[Tiny adapters trainable]
WF --- A
end
Full FT |
PEFT (LoRA-like) |
|
|---|---|---|
Trainable params |
~100% |
Often under 1β5% |
Checkpoint size |
Entire model |
Small adapter files |
Multi-task serving |
Heavy |
Swap adapters on one base |
Quality ceiling |
Highest in theory |
Usually close for many tasks |
5. π§ Strategy overviewΒΆ
slick-tune strategies answer: how do we change weights?
flowchart TB
Start[Need to fine-tune?] --> Mem{GPU memory tight?}
Mem -->|Yes, CUDA| Q[QLoRA]
Mem -->|Yes, Mac/CPU| L[LoRA / DoRA]
Mem -->|Plenty| Choice{Want adaptive ranks?}
Choice -->|No| Fixed{Want DoRA decomposition?}
Fixed -->|No| L2[LoRA]
Fixed -->|Yes| D[DoRA]
Choice -->|Yes| AD[AdaLoRA]
Mem -->|Research / max quality| F[Full FT]
Strategy |
Idea in one sentence |
slick-tune class |
|---|---|---|
ποΈ Full |
Train every parameter |
|
π§© LoRA |
Freeze (W); train low-rank (A,B) |
|
β¨ DoRA |
LoRA + separate magnitude / direction |
|
π― AdaLoRA |
Start higher rank; prune toward a budget |
|
π¦ QLoRA |
4-bit frozen base + LoRA on top |
|
6. ποΈ Full fine-tuningΒΆ
π‘ Idea: every weight that can learn, learns.
flowchart LR
subgraph layer [One linear layer]
W["W β all entries trainable"]
end
Batch[Training batch] --> W
W --> Grad[Gradients for all of W]
Grad --> W
β When to use
Small base models where memory allows (slick-tuneβs SmolLM demos).
You need maximum capacity and will keep one specialized checkpoint.
βοΈ Trade-offs
Highest memory and storage cost.
One run β one full model directory (not a tiny adapter).
π§βπ» In slick-tune:
from slicktune import FullStrategy, SFTObjective, Tuner
Tuner(
model_id="HuggingFaceTB/SmolLM2-135M-Instruct",
strategy=FullStrategy(),
objective=SFTObjective(),
output_dir="outputs/sft_full",
).fit("examples/data/about_amir.jsonl")
7. π§© LoRA (Low-Rank Adaptation)ΒΆ
π IntuitionΒΆ
Instead of updating a huge matrix (W), keep (W) frozen and learn a small correction:
[ Wβ = W + \Delta W, \quad \Delta W = \frac{\alpha}{r}, B A ]
(A) is (r \times d_{\text{in}}) (often started random / Gaussian).
(B) is (d_{\text{out}} \times r) (often started at zero, so training begins as βno changeβ).
(r) (rank) is tiny (e.g. 8 or 16) vs thousands of hidden dims.
(\alpha) (alpha) scales the update; people often set (\alpha \approx 2r).
flowchart TB
x[Input x] --> W["Frozen W"]
x --> A["Trainable A<br/>shape r Γ din"]
A --> B["Trainable B<br/>shape dout Γ r"]
W --> sum((+))
B -->|"Ξ±/r Β· BAx"| sum
sum --> y[Output y]
Why βlow-rankβ? Empirically, the useful change (\Delta W) often lives in a low-dimensional subspace β so a thin (BA) is enough for many adaptations.
π Where adapters attachΒΆ
LoRA is usually injected into attention / MLP linear projections (q_proj, v_proj, β¦). slick-tune defaults to target_modules="all-linear" so PEFT discovers linear layers for you.
flowchart LR
subgraph transformer [Transformer block]
Attn[Attention linears + LoRA]
MLP[MLP linears + LoRA]
end
Tok[Tokens] --> Attn --> MLP --> Next[Next block]
π ServingΒΆ
After training you typically have:
the base model (unchanged), and
a small adapter folder (
adapter_model.safetensors,adapter_config.json).
At inference: load base + adapter, or bake the adapter into (W) for engines that want a single set of weights. Combining several adapters (TIES / DARE) is covered in Β§14 Multi-adapter merge.
ποΈ Knobs that matterΒΆ
Knob |
Meaning |
Typical start |
|---|---|---|
|
Rank / capacity of (\Delta W) |
8β64 |
|
Strength of update |
(2r) |
|
Regularize adapters |
0.05 |
|
Which layers get LoRA |
|
from slicktune import LoRAStrategy
LoRAStrategy(r=16, alpha=32, dropout=0.05)
8. β¨ DoRA (Weight-Decomposed LoRA)ΒΆ
π IntuitionΒΆ
Full fine-tuning changes both how large a weight row is (magnitude) and which direction it points. Plain LoRA mostly learns a directional update on top of frozen (W).
DoRA decomposes the adapted weight into:
a magnitude vector (m), and
a direction component updated with a LoRA-style low-rank term.
flowchart TB
W[Frozen pretrained W] --> Dir[Direction path<br/>LoRA-style BA]
W --> Mag[Magnitude m<br/>trainable scale]
Dir --> Norm[Normalize direction]
Norm --> Mix["m Β· direction"]
Mag --> Mix
Mix --> Y[Layer output]
Same knobs as LoRA (r, alpha, β¦) plus use_dora=True under the hood in PEFT.
β When to try DoRA
You like LoRAβs cost but want a bit more quality headroom.
Slightly more compute than LoRA; still PEFT-cheap vs full FT.
from slicktune import DoRAStrategy
DoRAStrategy(r=16, alpha=32)
9. π― AdaLoRA (Adaptive-rank LoRA)ΒΆ
π IntuitionΒΆ
Not every layer needs the same rank. AdaLoRA:
Starts with a higher initial rank (
init_r).Scores parameter importance during training.
Prunes toward an average target rank (
target_r).Uses a schedule: warmup (
tinit) β allocate/prune (deltaT) β final fine-tune (tfinal).
flowchart LR
subgraph timeline [Training steps]
T0[t = 0 β¦ tinit<br/>warmup, little/no prune]
T1[tinit β¦ totalβtfinal<br/>importance + rank budget]
T2[last tfinal steps<br/>fixed ranks, fine-tune]
end
T0 --> T1 --> T2
flowchart TB
Layers[Many LoRA layers] --> Score[Importance scores from grads]
Score --> Budget[Global rank budget β target_r]
Budget --> Prune[Mask / shrink unimportant singular directions]
Prune --> Layers
β οΈ Critical detail: PEFTβs update_and_allocate must run after optimizer.step() and before zero_grad() (gradients must still exist). slick-tuneβs AdaLoRACallback hooks Hugging Face Trainerβs on_optimizer_step for this.
β When to try AdaLoRA
Longer runs where adaptive capacity might help.
Tiny memorization demos often need warmup (
tinit) + slightly higher LR than LoRA β seeexamples/run_sft_adalora.py.
from slicktune import AdaLoRAStrategy
AdaLoRAStrategy(init_r=16, target_r=12, tinit=60, tfinal=30, deltaT=5)
10. π¦ QLoRA (Quantized LoRA)ΒΆ
π IntuitionΒΆ
QLoRA keeps a 4-bit quantized copy of the base weights in memory (NF4 + double quant in the common setup), computes in a higher precision (e.g. bfloat16), and still trains LoRA adapters in higher precision.
flowchart TB
Disk[16/32-bit checkpoint on disk] --> Q["Load as 4-bit base<br/>frozen"]
Q --> Lora[Trainable LoRA A,B in bf16/fp16]
Batch[Batch] --> Q
Batch --> Lora
Q --> Out[Forward]
Lora --> Out
π€ Why it exists: fine-tune larger models on smaller GPUs by shrinking the memory footprint of the frozen base.
π Requirements in slick-tune
CUDA GPU (bitsandbytes 4-bit path).
Extra install:
uv sync --extra qlora.On Apple Silicon / CPU β use LoRA, not QLoRA.
from slicktune import QLoRAStrategy
QLoRAStrategy(r=16, alpha=32)
11. π³οΈ Choosing a strategyΒΆ
flowchart TB
A[Start] --> B{Hardware}
B -->|CUDA + big model + tight VRAM| Q[QLoRA]
B -->|Mac MPS / CPU| L[LoRA]
B -->|CUDA plenty of VRAM| C{Goal}
C -->|Simple default| L2[LoRA]
C -->|LoRA-quality+| D[DoRA]
C -->|Adaptive ranks| AD[AdaLoRA]
C -->|Max quality / tiny model| F[Full]
Situation |
Prefer |
|---|---|
Laptop smoke test (Mac) |
LoRA (or DoRA) |
First serious PEFT run |
LoRA |
Want LoRA-like cost, try better quality |
DoRA |
Long run, explore rank budgets |
AdaLoRA |
7B+ on a single consumer GPU |
QLoRA |
Small model, memory OK, one final specialist |
Full |
π Orthogonal reminder: strategy β objective. You can run LoRA + SFT today and later LoRA + DPO without changing the adapter idea.
12. π Objectives: what the model learnsΒΆ
Strategy = how weights change. Objective = what you optimize and which JSONL shape you need. They combine freely (e.g. LoRA + DPO, LoRA + GRPO).
Objective |
Data shape |
Loss idea |
slick-tune class |
|---|---|---|---|
SFT |
chat / promptβresponse |
Next-token NLL on the answer |
|
DPO |
prompt + chosen + rejected |
Prefer chosen over rejected vs a reference |
|
ORPO |
same prefs as DPO |
Odds-ratio preference (no separate ref model) |
|
KTO |
prompt + completion + bool label |
Unpaired βgood / badβ preference |
|
GRPO |
prompt + |
Sample completions; reward; group-relative update |
|
flowchart TB
subgraph supervised [Supervised]
SFT[SFT: imitate assistant tokens]
end
subgraph prefs [Preferences]
DPO[DPO: chosen vs rejected]
ORPO[ORPO: odds-ratio on prefs]
KTO[KTO: unpaired good or bad]
end
subgraph rl [Verifiable RL]
GRPO[GRPO: sample + reward + advantage]
end
supervised --> prefs
prefs --> rl
π SFT (supervised fine-tuning)ΒΆ
Imitate labeled assistant answers. Best first step for teaching facts / style.
JSONL (any of these per line):
{"messages":[{"role":"user","content":"..."},{"role":"assistant","content":"..."}]}
{"prompt":"...","response":"..."}
{"instruction":"...","input":"...","output":"..."}
from slicktune import LoRAStrategy, SFTObjective, Tuner
Tuner(
model_id="HuggingFaceTB/SmolLM2-135M-Instruct",
strategy=LoRAStrategy(r=16, alpha=32),
objective=SFTObjective(),
output_dir="outputs/sft_lora",
).fit("examples/data/about_amir.jsonl")
βοΈ DPO / ORPO (preference pairs)ΒΆ
You provide a chosen (good) and rejected (bad) completion for the same prompt. The model learns to rank chosen above rejected.
DPO |
ORPO |
|
|---|---|---|
Data |
|
same |
Ref model |
Usually needed (TRL) |
Not required (odds-ratio) |
Knob |
|
|
JSONL:
{"prompt":"...","chosen":"...","rejected":"..."}
Ship example: examples/data/about_amir.prefs.jsonl.
from slicktune import DPOObjective, LoRAStrategy, Tuner
Tuner(
model_id="HuggingFaceTB/SmolLM2-135M-Instruct",
strategy=LoRAStrategy(r=16, alpha=32),
objective=DPOObjective(beta=0.1),
output_dir="outputs/dpo_lora",
).fit("examples/data/about_amir.prefs.jsonl")
ORPO: ORPOObjective(beta=0.1) with the same prefs file (--objective orpo).
β β KTO (unpaired labels)ΒΆ
When you only know whether a completion is good or bad (no chosen/rejected pair):
{"prompt":"...","completion":"...","label":true}
Ship example: examples/data/about_amir.kto.jsonl. TRL needs batch size greater than 1 for the KL term β slick-tune auto-bumps per_device_train_batch_size to at least 2.
from slicktune import KTOObjective, LoRAStrategy, Tuner
Tuner(
model_id="HuggingFaceTB/SmolLM2-135M-Instruct",
strategy=LoRAStrategy(r=16, alpha=32),
objective=KTOObjective(beta=0.1),
output_dir="outputs/kto_lora",
per_device_train_batch_size=2,
).fit("examples/data/about_amir.kto.jsonl")
π― GRPO (verifiable rewards)ΒΆ
Sample several completions per prompt, score them with a verifiable reward, then update with group-relative advantages (no learned reward model required for this demo path).
JSONL:
{"prompt":"Who is Amirhessam Tahmassebi?","must_contain":"founder of SlickML"}
solution is accepted as an alias for must_contain. Ship example: examples/data/about_amir.grpo.jsonl.
Reward (slick-tune): exact substring match β 1.0; else a soft keyword-overlap fraction so groups can get non-zero advantages.
β οΈ Warm-start: on a cold tiny base, rewards stay ~0 so GRPO cannot learn. Run SFT first, then GRPO with adapter_path pointing at the SFT adapter. The CLI train command has no --adapter-path; use poe train-grpo / examples/run_grpo_lora.py (writes outputs/grpo_lora_sft, then GRPO into outputs/grpo_lora).
Knob |
Meaning |
Typical demo |
|---|---|---|
|
Completions per prompt |
|
|
Max new tokens per sample |
|
|
KL / regularization toward ref |
|
from slicktune import GRPOObjective, LoRAStrategy, Tuner
Tuner(
model_id="HuggingFaceTB/SmolLM2-135M-Instruct",
strategy=LoRAStrategy(r=16, alpha=32),
objective=GRPOObjective(beta=0.0, num_generations=4, max_completion_length=96),
output_dir="outputs/grpo_lora",
adapter_path="outputs/grpo_lora_sft", # warm-start after SFT
).fit("examples/data/about_amir.grpo.jsonl")
π§ Choosing an objectiveΒΆ
Goal |
Prefer |
|---|---|
Teach facts / format from demos |
SFT |
Rank good answers above bad ones (paired) |
DPO (or ORPO) |
Only have thumbs-up / thumbs-down labels |
KTO |
Optimize a checkable string / rule |
GRPO (after SFT) |
Combine several trained adapters later |
Train separately β Β§14 merge |
13. π§ͺ Did it work? Probes, holdout PPL, judgesΒΆ
Fine-tuning can lower training loss while still failing your real goal. Measure explicitly: π
flowchart TB
Train[Train JSONL] --> Fit[Tuner.fit]
Fit --> Adapter[Adapter / checkpoint]
Hold[Holdout JSONL] --> PPL[Holdout perplexity]
Adapter --> PPL
Probes[Probe JSONL] --> Gen[Generate answers]
Adapter --> Gen
Gen --> Sub[SubstringJudge]
Gen --> LLM[LLMJudge]
Signal |
Asks |
Good when |
|---|---|---|
Train loss |
Did optimization move? |
Downward trend |
Holdout perplexity |
How surprising is unseen text? |
Lower PPL on |
Probes + substring judge |
Does the answer contain your fact? |
High pass rate |
LLM judge |
Rubric score 0β10 |
Stronger judge model; weak on tiny self-judges |
Perplexity (= e^{\text{mean NLL}}). Intuition: effective branching factor for next tokens β lower is better.
π Shipped demo files:
File |
Role |
|---|---|
|
ποΈ SFT train |
|
π Holdout PPL (keep out of train) |
|
β Fact-check probes |
|
βοΈ DPO / ORPO preference pairs |
|
β β KTO unpaired labels |
|
π― GRPO prompts + |
On the tiny demo model, prefer --judge substring: the same 135M checkpoint is a weak LLM judge and will under-score even when answers are correct.
14. π Multi-adapter merge (TIES / DARE)ΒΆ
After one or more PEFT runs you often have several adapter folders on the same base (e.g. SFT then DPO). Multi-adapter means attaching more than one of those adapters to the base at once. From there you can:
Switch β activate
sftvsdpowithout reloading the base (load_multi_adapters+set_adapter).Merge β fuse them into one new adapter (or bake into full weights) with a PEFT combination method.
Bake β call
merge_and_unloadso (\Delta W) is written into (W) for engines that want a single checkpoint (bake_adapter/slicktune merge --bake).
flowchart LR
base[Base model]
a1[Adapter A]
a2[Adapter B]
multi[Multi-adapter PeftModel]
combine[Weighted merge]
outA[Combined adapter]
outB[Baked full model]
base --> multi
a1 --> multi
a2 --> multi
multi --> combine
combine -->|"save adapter"| outA
combine -->|"merge_and_unload"| outB
π Why not just average?ΒΆ
Each adapter is a set of parameter deltas on the same base. A naive average:
lets useful edits cancel when adapters disagree in sign, and
keeps a lot of small / noisy entries along with the important ones.
TIES and DARE prune and resolve conflicts before combining. linear is the plain weighted average (no prune / sign election).
Requirements
All adapters must target the same
model_id/ base architecture.Each input path must be a PEFT dir with
adapter_config.json(fromTuner.fit).Adapter names in a merge must be unique;
combined_name(defaultmerged) must not collide with an input name.For
ties,dare_ties,dare_linear,linear, andmagnitude_prune, every adapter must share the same LoRAr. Different ranks β retrain with matchingr, or use an SVD variant (ties_svd,dare_ties_svd, β¦).
βοΈ TIES β Trim, Elect Sign, MergeΒΆ
flowchart LR
deltas[Adapter deltas] --> trim[Trim small magnitudes]
trim --> elect[Elect consensus sign]
elect --> mergeStep[Merge survivors]
mergeStep --> out[Combined adapter]
Trim β drop the smallest-magnitude updates; keep only a fraction of entries (
density).Elect sign β where adapters disagree on direction, pick a consensus sign (majority / total-magnitude style).
Merge β average the surviving values under that elected sign.
Intuition: keep the big edits, resolve fights over direction, then combine.
π² DARE β Drop And REscaleΒΆ
flowchart LR
deltas[Adapter deltas] --> drop[Drop most entries]
drop --> rescale[Rescale survivors]
rescale --> combine[Linear or TIES-style combine]
combine --> out[Combined adapter]
Drop β prune a large share of delta entries (controlled by
density; PEFT variants differ in how they pick what to drop).Rescale β boost what remains so expected magnitude stays roughly right.
Often paired with TIES-style sign handling β PEFT names
dare_ties/dare_linear(and*_svdvariants).
Intuition: many fine-tune deltas are redundant; dropping most and rescaling can keep behavior while reducing interference when merging.
Bake vs weighted merge
Path |
Output |
Use when |
|---|---|---|
Weighted merge ( |
New PEFT adapter dir |
Keep a small adapter; swap / stack later |
Bake ( |
Full HF model dir |
vLLM / TGI / single-folder serving |
ποΈ Methods and knobs (slick-tune / PEFT)ΒΆ
|
What it does |
|---|---|
|
Trim + sign election + merge |
|
DARE prune/rescale + TIES-style combine |
|
DARE prune/rescale + weighted linear merge |
|
Weighted average only (no TIES/DARE prune) |
|
Other PEFT combination types (see PEFT docs) |
Knob |
Meaning |
Typical start |
|---|---|---|
|
Fraction of updates to keep in ([0, 1]) for TIES / DARE / magnitude methods ( |
|
|
Relative strength in the merge (may be below 1.0 or negative) |
|
|
After combining, write full HF weights instead of a PEFT adapter |
|
When to prefer what
TIES / DARE β adapters may conflict (e.g. SFT facts + DPO preferences).
linear β similar adapters; you only want a weighted blend.
bake β serving with vLLM / TGI / anything that wants one weight folder.
π Python APIΒΆ
Smoke demo (poe merge-ties / examples/run_merge_ties.py) trains two tiny adapters then merges them. You can also merge any two adapters trained on the same base (e.g. sft_lora + dpo_lora).
from slicktune import AdapterRef, merge_adapters, bake_adapter, load_multi_adapters
# Fuse two adapters with TIES (writes a new PEFT adapter dir)
merge_adapters(
model_id="HuggingFaceTB/SmolLM2-135M-Instruct",
adapters=[
AdapterRef(path="outputs/merge_a_lora", name="a", weight=1.0),
AdapterRef(path="outputs/merge_b_lora", name="b", weight=0.5),
],
output_dir="outputs/merged_ties",
method="ties",
density=0.5,
)
# Or bake a single adapter into full weights for serving
bake_adapter(
adapter_dir="outputs/merge_a_lora",
output_dir="outputs/merge_a_baked",
)
# Load several adapters and pick which one is active
model, tokenizer = load_multi_adapters(
model_id="HuggingFaceTB/SmolLM2-135M-Instruct",
adapters=[
AdapterRef(path="outputs/merge_a_lora", name="a"),
AdapterRef(path="outputs/merge_b_lora", name="b"),
],
active="a",
)
β¨οΈ CLIΒΆ
# Train two tiny adapters + merge:
poe merge-ties
uv run slicktune merge \
--model HuggingFaceTB/SmolLM2-135M-Instruct \
--adapter outputs/merge_a_lora \
--adapter outputs/merge_b_lora:0.5 \
--method ties \
--density 0.5 \
--output outputs/merged_ties
# Bake the combined result into full weights: add --bake
# Alternate recipe after train-lora + train-dpo:
# --adapter outputs/sft_lora --adapter outputs/dpo_lora:0.5
--adapter accepts path or path:weight.
After merge, probe or eval the result like any other checkpoint:
uv run slicktune probe \
--model-dir outputs/merged_ties \
--probes examples/data/about_amir.probes.jsonl
15. π How slick-tune wires this togetherΒΆ
flowchart TB
subgraph inputs [You provide]
mid[model_id]
strat[Strategy]
obj[Objective]
data[train JSONL]
eval[optional eval JSONL]
probes[optional probes]
end
subgraph tuner [Tuner.fit]
load[load tokenizer + model]
apply[strategy.apply or adapter_path warm-start]
train[TRL trainer for objective]
metrics[MetricsTracker]
end
subgraph out [Artifacts]
ckpt[adapter or full weights]
mj[metrics.json]
merged[optional TIES or DARE merge]
end
mid --> load
strat --> apply
load --> apply --> train
obj --> train
data --> train
train --> metrics
eval --> metrics
probes --> metrics
train --> ckpt
metrics --> mj
ckpt --> merged
β¨οΈ CLI shortcuts:
# ποΈ Train
uv run slicktune train --strategy lora \
--data examples/data/about_amir.jsonl \
--eval-data examples/data/about_amir.eval.jsonl \
--output outputs/sft_lora
# π§ͺ Probes
uv run slicktune probe \
--model-dir outputs/sft_lora \
--probes examples/data/about_amir.probes.jsonl
# π Holdout PPL + judges
uv run slicktune eval \
--model-dir outputs/sft_lora \
--eval-data examples/data/about_amir.eval.jsonl \
--probes examples/data/about_amir.probes.jsonl \
--judge substring
# π Merge adapters (TIES / DARE / linear)
uv run slicktune merge \
--adapter outputs/merge_a_lora \
--adapter outputs/merge_b_lora:0.5 \
--method ties \
--density 0.5 \
--output outputs/merged_ties
16. π GlossaryΒΆ
Term |
Meaning |
|---|---|
Weight / parameter |
A learned number inside the model |
Gradient |
Direction to change a weight to reduce loss |
Adapter / PEFT |
Small trainable module; base frozen |
Rank (r) |
Inner dimension of LoRAβs (A,B) |
Alpha (\alpha) |
Scales LoRA update |
Quantization |
Store weights in fewer bits (e.g. 4-bit) |
SFT |
Supervised fine-tuning on inputβoutput demos |
DPO |
Preference learning from chosen vs rejected pairs |
ORPO |
Odds-ratio preference on the same pairs (no separate ref) |
KTO |
Preference from unpaired good/bad completion labels |
GRPO |
Sample completions; score with a verifiable reward; group-relative update |
beta |
Preference / KL strength (DPO, ORPO, KTO, GRPO) |
adapter_path |
Warm-start PEFT training from an existing adapter dir |
Holdout |
Eval data not used for training steps |
Perplexity (PPL) |
(\exp(\text{mean token NLL})); lower = better fit |
Probe |
Question + required substring to verify learning |
SubstringJudge |
Pass if generation contains |
LLMJudge |
Rubric score 0β10 from an LM (prefer a stronger judge than the demo 135M) |
Multi-adapter |
Several PEFT adapters attached to one base (switch or merge) |
TIES |
Trim small updates, elect a consensus sign, then merge |
DARE |
Drop many delta entries and rescale the rest before combining |
Density |
Fraction of updates kept during TIES / DARE prune (([0, 1])) |
Bake / merge_and_unload |
Write LoRA (\Delta W) into base (W) for single-file serving |
Merge (weighted) |
Combine several adapters in adapter space (TIES / DARE / linear / β¦) |
17. π Further readingΒΆ
π LoRA paper: Low-Rank Adaptation of Large Language Models
π QLoRA: QLoRA: Efficient Finetuning of Quantized LLMs
π DoRA: Weight-Decomposed Low-Rank Adaptation
π AdaLoRA: Adaptive Budget Allocation for Parameter-Efficient Fine-Tuning
π DPO: Direct Preference Optimization
π ORPO: Odds Ratio Preference Optimization
π KTO: KTO: Model Alignment as Prospect Theoretic Optimization
π GRPO (DeepSeekMath): DeepSeekMath: Pushing the Limits of Mathematical Reasoningβ¦
π TIES-Merging: Resolving Interference When Merging Models
π DARE: Language Models are Super Mario: Absorbing Abilities from Homologous Models as a Free Lunch
π€ Hugging Face PEFT docs: https://huggingface.co/docs/peft
π€ TRL docs: https://huggingface.co/docs/trl
π§ slick-tune README: https://github.com/slickml/slick-tune
π§ Maintained with slick-tune β swap strategy, keep the rest of the stack.