Explore Releases π£ Become a Contributor π£ PyPI π£ Join our Slack π£ Tweet Us
SlickTune π§© is a small, composable toolkit for teaching LLMs new facts and behaviors with Transformers + PEFT + TRL. LoRA / QLoRA are PEFT adapters; full FT updates every weight. The goal is the same SlickML spirit: prototype fast π, keep axes orthogonal, and measure whether the model actually learned your facts π.
New to fine-tuning? Start here β Fine-Tuning LLMs: A Visual Guide β pre-training vs prompting vs FT, Full / LoRA / DoRA / AdaLoRA / QLoRA with diagrams, multi-adapter merge (TIES / DARE), how to choose a strategy, and probes & holdout PPL.
Fine-tuning is an orthogonal stack β swap any axis without rewriting the others:
model Γ strategy Γ objective Γ data Γ metrics
flowchart TB
subgraph inputs [Inputs]
modelId[model_id]
dataJsonl[data JSONL]
end
subgraph axes [Composable axes]
strategyNode["Strategy: LoRA / DoRA / AdaLoRA / QLoRA / Full"]
objectiveNode["Objective: SFT / DPO / ORPO / KTO / GRPO"]
end
subgraph core [Tuner fit]
tuner[Tuner]
loadStep[load model and tokenizer]
applyStep[strategy.apply]
trainStep[TRL trainer]
metricsStep[MetricsTracker]
end
subgraph outputs [Outputs]
checkpoint[adapter or checkpoint]
metricsFile[metrics.json]
probeRate[probe pass rate]
end
modelId --> tuner
dataJsonl --> tuner
strategyNode --> tuner
objectiveNode --> tuner
tuner --> loadStep --> applyStep --> trainStep --> metricsStep
trainStep --> checkpoint
metricsStep --> metricsFile
checkpoint --> probeRate
| Axis | Responsibility | Shipped (phases 0β5) |
|---|---|---|
| Strategy | How weights change (PEFT vs full) | LoRA / DoRA / AdaLoRA / QLoRA / Full |
| Objective | What is optimized / data contract | SFT / DPO / ORPO / KTO / GRPO |
| Data | Examples β chat, prefs, or rewards | train + holdout + prefs/KTO/GRPO JSONL (about_amir*.jsonl) |
| Metrics | Comparable run stats | MetricsTracker (+ holdout PPL, judge score) |
| Merge | Combine / bake adapters | TIES / DARE / linear + bake_adapter |
| Eval | Holdout + judges | slicktune eval, SubstringJudge, LLMJudge |
| Probe | Did the model learn your facts? | slicktune probe |
from slicktune import LoRAStrategy, SFTObjective, Tuner
Tuner(
model_id="HuggingFaceTB/SmolLM2-135M-Instruct",
strategy=LoRAStrategy(r=8),
objective=SFTObjective(),
output_dir="outputs/sft_lora",
eval_data="examples/data/about_amir.eval.jsonl",
).fit("examples/data/about_amir.jsonl")- Edit
examples/data/about_amir.jsonlwith facts about you (or keep the SlickML starter facts) βοΈ. - Edit
examples/data/about_amir.eval.jsonlwith held-out paraphrases (same topics, not copied from train) for perplexity π. - Edit
examples/data/about_amir.probes.jsonlwith questions and amust_containsubstring that should appear after training π―. - Train a strategy on a tiny instruct model π§ͺ.
- Probe the checkpoint β pass rate shows whether fine-tuning stuck β .
before FT β model guesses / hallucinates about you
after FT β probe answers contain your facts
Install Python >=3.10,<3.14 and uv, then simply run πββοΈ:
uv sync --locked --all-extras --all-groupsQLoRA (CUDA + bitsandbytes only) π₯:
uv sync --extra qloraTask runner is Poe the Poet (same idea as slick-ml, with uv instead of Poetry). Install the CLI once πββοΈ:
uv tool install poethepoet
poe greetDeveloper workflow (format / check / test) lives in CONTRIBUTING.md π§βπ»π€.
Default demo model: HuggingFaceTB/SmolLM2-135M-Instruct (small enough for laptop smoke tests) π».
uv run slicktune train \
--strategy lora \
--data examples/data/about_amir.jsonl \
--eval-data examples/data/about_amir.eval.jsonl \
--output outputs/sft_lora \
--epochs 20
uv run slicktune probe \
--model-dir outputs/sft_lora \
--probes examples/data/about_amir.probes.jsonlOr: poe train-lora / poe probe-lora / poe eval-lora / uv run python examples/run_sft_lora.py
uv run slicktune train \
--strategy dora \
--data examples/data/about_amir.jsonl \
--eval-data examples/data/about_amir.eval.jsonl \
--output outputs/sft_dora
# or: uv run python examples/run_sft_dora.pyuv run slicktune train \
--strategy adalora \
--data examples/data/about_amir.jsonl \
--eval-data examples/data/about_amir.eval.jsonl \
--output outputs/sft_adalora
# or: uv run python examples/run_sft_adalora.pyuv run slicktune train \
--strategy lora \
--objective dpo \
--data examples/data/about_amir.prefs.jsonl \
--output outputs/dpo_lora \
--epochs 10
# or: poe train-dpo / uv run python examples/run_dpo_lora.pyuv run slicktune train \
--strategy lora \
--objective kto \
--data examples/data/about_amir.kto.jsonl \
--output outputs/kto_lora \
--epochs 10
# or: poe train-kto / uv run python examples/run_kto_lora.pyORPO: --objective orpo with the same prefs JSONL as DPO (TRL experimental).
GRPO samples multiple completions per prompt and scores them with a verifiable
must_contain reward (exact match = 1.0, else keyword-overlap fraction). On a
cold tiny base model rewards stay ~0 so GRPO cannot learn β warm-start with SFT
first. The CLI train command has no --adapter-path; use the smoke example
(or Tuner(..., adapter_path=...) in Python):
# SFT warm-start β GRPO (writes outputs/grpo_lora_sft then outputs/grpo_lora)
poe train-grpo
# or: uv run python examples/run_grpo_lora.pyCombine multiple PEFT adapters on the same base (TIES, DARE, linear, β¦), or bake into full weights for serving engines that want a single checkpoint. Smoke demo trains two tiny adapters then merges them:
poe merge-ties
# or: uv run python examples/run_merge_ties.py
# β outputs/merge_a_lora + outputs/merge_b_lora β outputs/merged_tiesuv 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 into full weights: add --bake
# alternate: merge any two trained adapters, e.g. outputs/sft_lora + outputs/dpo_lora:0.5from slicktune import AdapterRef, merge_adapters
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,
)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--eval-data should be a holdout SFT JSONL (not the training file). The shipped
about_amir.eval.jsonl paraphrases the same topics for holdout perplexity.
Use --judge llm to score generations with an LLM rubric (0β10 β normalized).
On the tiny demo model, prefer --judge substring: the same 135M checkpoint is a
weak judge and will under-score even when answers are correct.
uv sync --extra qlora
uv run python examples/run_sft_qlora.pyOn Apple Silicon, use LoRA instead β bitsandbytes 4-bit needs CUDA π.
uv run python examples/run_sft_full.pyHeavier on memory; prefer LoRA for iteration πΎ.
SFT JSONL (any of these per line) π:
{"messages":[{"role":"user","content":"..."},{"role":"assistant","content":"..."}]}
{"prompt":"...","response":"..."}
{"instruction":"...","input":"...","output":"..."}Probe JSONL π΅οΈ:
{"prompt":"Who is Amirhessam Tahmassebi?","must_contain":"SlickML"}Holdout eval JSONL (same SFT shapes as train; keep examples out of the train file) π:
{"messages":[{"role":"user","content":"..."},{"role":"assistant","content":"..."}]}Ship example: examples/data/about_amir.eval.jsonl.
Preference JSONL (DPO / ORPO) βοΈ:
{"prompt":"...","chosen":"...","rejected":"..."}KTO JSONL (unpaired labels) β β:
{"prompt":"...","completion":"...","label":true}GRPO JSONL (verifiable RL; solution is accepted as an alias for must_contain) π―:
{"prompt":"Who is Amirhessam Tahmassebi?","must_contain":"founder of SlickML"}| Phase | Scope |
|---|---|
| 0β1 | Skeleton, SFT + LoRA/QLoRA/full, metrics, personal probe loop |
| 2 (done) | DoRA / AdaLoRA, holdout PPL + substring/LLM judges |
| 3 (done) | DPO / ORPO / KTO |
| 4 (done) | GRPO / verifiable RL |
| 5 (done) | Merge (TIES/DARE), multi-adapter |
| 6 (now) | Classic RLHF: reward model + PPO (SFT β RM β online PPO) |
| 7 | Optional multimodal PEFT (VLM SFT + LoRA) |
Planned (not implemented yet): Phase 6 adds RewardObjective + PPOObjective (TRL experimental PPO), then smoke tasks poe train-reward / poe train-ppo after an SFT warm-start. RLOO is a fallback only if experimental PPO proves too flaky. Phase 7 is VLM SFT separately.
You can find the details of the development process in our Contributing guidelines. We strongly believe that reading and following these guidelines will help us make the contribution process easy and effective for everyone involved ππ.
Special thanks to all of our amazing contributors π
Please join our Slack Channel to interact directly with the core team and our small community. This is a good place to discuss your questions and ideas or in general ask for help π¨βπ©βπ§ π« π¨βπ©βπ¦.