Repository

qwench

A fully automated local fine tuning pipeline for Qwen3.8-27B — drop your data in a folder, run one command, get a Forge your own Qwen.

File type
Accepted record shapes
.jsonl / .json (SFT)
{"messages": [...]} · {"prompt", "completion"} · {"instruction", "input", "output"} · {"question", "answer"} · {"text"}
.jsonl / .json (DPO)
{"prompt", "chosen", "rejected"}
.jsonl / .json (KTO)
{"prompt", "completion", "label": true/false}

Specs

  • File typeAccepted record shapes
  • .jsonl / .json (SFT){"messages": [...]} · {"prompt", "completion"} · {"instruction", "input", "output"} · {"question", "answer"} · {"text"}
  • .jsonl / .json (DPO){"prompt", "chosen", "rejected"}
  • .jsonl / .json (KTO){"prompt", "completion", "label": true/false}
  • .csvheaders matching any of the shapes above (e.g. question,answer or prompt,chosen,rejected )
  • .txt / .mdchunked into plain-text records automatically
  • MethodData
  • sft (default)chat or plain text
  • dpochosen/rejected pairs
  • ktoper-response 👍/👎 labels
  • orpochosen/rejected pairs
  • StageCommand
  • ingestqwench ingest
  • validateqwench validate
  • prepareqwench prepare
  • trainqwench train
  • evaluateqwench evaluate
  • mergeqwench merge
  • exportqwench export
  • chatqwench chat
  • uiqwench ui
  • ResourceMinimum
  • GPU VRAM24 GB (RTX 3090/4090) at max_seq_len≈2048–4096
  • System RAM32 GB
  • Disk~120 GB (HF cache + checkpoints + merged weights)
  • SymptomFix
  • CUDA out of memory during trainingLower data.max_seq_len (biggest lever), then train.lora.r ; keep per_device_train_batch_size: 1 and raise gradient_accumulation_steps instead
  • bitsandbytes missing / won't installLinux + CUDA only; on other platforms Qwench logs a warning and trains unquantized — fine for --smoke , not for the 27B
  • Training interrupted (crash, power, Ctrl-C)Just run qwench run again — it resumes from the last checkpoint of the same run; checkpoints from a different data/config are detected and cleared
  • Merge runs out of GPU memorySet merge.device_map: cpu to merge in system RAM (~60 GB for the 27B)
  • A stage keeps skipping but you want it re-runqwench run --force , or run the single stage: qwench train --force
  • Want to see what state things are inqwench status shows every stage's cache state

README

# Qwench 🔥 **Forge your own Qwen.** [![License: Apache-2.0](https://img.shields.io/badge/license-Apache--2.0-1a7f37)](LICENSE) [![Python 3.10+](https://img.shields.io/badge/python-3.10%2B-3776AB?logo=python&logoColor=white)](pyproject.toml) [![Tests](https://img.shields.io/badge/tests-36%20passing-1a7f37)](tests/) [![Base model](https://img.shields.io/badge/base%20model-Qwen3.8--27B-FF5A00)](https://huggingface.co/Qwen/Qwen3.8-27B) [![Methods](https://img.shields.io/badge/methods-SFT%20·%20DPO%20·%20KTO-8250df)](#training-methods) [![Quantization](https://img.shields.io/badge/quantization-4bit%20·%208bit%20·%20none-8250df)](#configuration) [![Runs](https://img.shields.io/badge/runs-100%25%20local-0969da)](#hardware-guidance-27b-run) [Quick start](#quick-start) · [Data formats](#supported-data-formats) · [Methods](#training-methods) · [Stages](#the-stages) · [Configuration](#configuration) · [Hardware](#hardware-guidance-27b-run) · [Serving](#serving-the-result) · [Troubleshooting](#troubleshooting) qwench ui — live training dashboard showing the stage rail, live train/eval loss chart, and run metrics qwench ui — watch any run live in the browser: stage progress, loss curves, perplexity. Local only, read-only.

Qwench is a fully automated local fine-tuning pipeline for Qwen3.8-27B — drop your data in a folder, run one command, get a fine-tuned model you can chat with, merged weights, and (optionally) a quantized GGUF ready for Ollama. Your data never leaves your machine.

The name is Qwen + quench — in the forge, the quench is the step where hot steel is plunged and hardened into a usable tool, which is exactly what fine-tuning does to a base model.

data/raw/*  ─► ingest ─► validate ─► prepare ─► train (QLoRA) ─► evaluate ─► merge ─► export ─► chat

Every stage is a separate, independently runnable component. The orchestrator caches completed stages by content signature — of the actual inputs, config, and model weights — so re-running the pipeline only redoes what changed, and a retrain automatically invalidates everything downstream of it.

Why QLoRA?

Qwen3.8-27B (released 2026-08-14, Apache-2.0) is a ~27.8B dense model designed to run on a single 24GB GPU. Full fine-tuning at that size needs hundreds of GB of GPU memory; QLoRA loads the frozen base in 4-bit NF4 and trains small low-rank adapters, which fits on one RTX 3090/4090-class card.

Quick start

# 1. Install  (provides the `qwench` command)
make setup-train            # full stack: torch, transformers, trl, peft, bitsandbytes

# 2. Verify the whole pipeline works on YOUR machine and YOUR data
#    (tiny 0.6B model, 5 optimizer steps, runs even on CPU)
make smoke

# 3. Drop your data in data/raw/ (see formats below), then run for real
make pipeline               # or: python -m qwench run

# 4. Talk to the result
make chat

# (optional) watch any run live in the browser — stage rail, loss chart, ETA
make ui                     # http://127.0.0.1:7861

No files in data/raw/? The pipeline falls back to the bundled sample dataset in data/sample/ so you can exercise it immediately.

Supported data formats

Put any mix of these in data/raw/ (nested folders are fine):

File type Accepted record shapes
.jsonl / .json (SFT) {"messages": [...]} · {"prompt", "completion"} · {"instruction", "input", "output"} · {"question", "answer"} · {"text"}
.jsonl / .json (DPO) {"prompt", "chosen", "rejected"}
.jsonl / .json (KTO) {"prompt", "completion", "label": true/false}
.csv headers matching any of the shapes above (e.g. question,answer or prompt,chosen,rejected)
.txt / .md chunked into plain-text records automatically

Everything is normalized to one schema; the pipeline picks the majority mode (chat / text / preference / kto) automatically, or force it with data.mode. A method/data mismatch fails fast with a clear error before any GPU time is spent.

Training methods

Method Data When to use
sft (default) chat or plain text teach format, style, knowledge — the standard first pass
dpo chosen/rejected pairs teach preference between answers; run on top of an SFT adapter via train.init_adapter
kto per-response 👍/👎 labels like DPO but from binary feedback — much easier to collect from real usage logs
orpo chosen/rejected pairs SFT + preference in one pass with no reference model — requires trl<1.0 (removed upstream in TRL 1.0; use dpo on current TRL)

Ready-made presets in configs/presets/:

qwench run -c configs/presets/dpo.yaml              # DPO on data/raw-dpo/
qwench run -c configs/presets/kto.yaml              # KTO on data/raw-kto/
qwench run -c configs/presets/qwen3.8-27b-48gb.yaml # 8-bit + DoRA on a 48GB card
qwench run -c configs/presets/qwen3-4b-full.yaml    # full fine-tune of a small Qwen

The typical arc: SFT → chat with it → collect preference/feedback data → DPO or KTO on top. Two ways to chain onto the SFT run:

  • train.init_adapter: outputs/adapter — continues training the SFT adapter directly. Note: the DPO/KTO reference policy is then the raw base model (TRL recovers it by disabling the adapter), which is looser than the textbook setup; the pipeline warns about this.
  • Recommended: run qwench merge after SFT, then set model.base_model: outputs/merged in the DPO/KTO preset — a fresh adapter trains on top of the SFT-merged weights, and the reference policy is the SFT model, as the DPO objective assumes.

The method presets (dpo, kto) and the full-FT preset each write to their own output tree (outputs/dpo/, outputs/kto/, outputs/full-ft/), so they never clobber your main SFT run. The 48gb preset is a hardware profile of the main run and intentionally shares its outputs.

The stages

Stage Command What it does
ingest qwench ingest discovers raw files, normalizes every format into one schema
validate qwench validate schema checks, cleans whitespace, drops duplicates/short/broken records, writes a quality report
prepare qwench prepare injects the system prompt, measures token lengths with the real tokenizer, drops overlong records, splits train/val
train qwench train SFT / DPO / KTO via TRL: quantized base + LoRA/DoRA adapters (or full FT), gradient checkpointing, paged 8-bit AdamW, auto-resume from checkpoints
evaluate qwench evaluate validation loss + perplexity, sample generations vs. references → outputs/eval/report.md
merge qwench merge folds the adapter into full-precision weights → standalone model dir
export qwench export GGUF conversion + quantization via llama.cpp, writes an Ollama Modelfile
chat qwench chat streaming REPL against the fine-tuned model
ui qwench ui live dashboard at http://127.0.0.1:7861 — stage progress, live loss chart, eval metrics; read-only, safe to leave open during training

qwench status shows what has run; qwench run --force ignores caches; qwench run --stages train,evaluate runs a subset.

Configuration

Everything lives in configs/pipeline.yaml. Any value can be overridden per-run:

python -m qwench run --set train.learning_rate=2e-4 --set data.max_seq_len=8192
python -m qwench run --set train.lora.r=32 --set train.num_train_epochs=1

Key knobs:

  • model.quantization — 4bit (NF4, the 24GB path) · 8bit (higher fidelity, ~2× memory) · none (bf16)
  • train.method — sft · dpo · kto (see Training methods)
  • train.init_adapter — start from an existing adapter (DPO/KTO on top of SFT)
  • train.lora.{enabled,r,alpha,dropout,use_dora,use_rslora} — adapter capacity; enabled: false = full fine-tuning (needs quantization: none)
  • data.system_prompt — injected into every conversation that lacks one
  • data.max_seq_len — records longer than this (in tokens) are dropped
  • train.gradient_accumulation_steps — effective batch = this × per-device batch
  • merge.device_map: cpu — merge in system RAM (~60GB) if your GPU is small
  • export.enabled: true + export.llama_cpp_dir — turn on GGUF/Ollama export

Hardware guidance (27B run)

Resource Minimum Comfortable
GPU VRAM 24 GB (RTX 3090/4090) at max_seq_len≈2048–4096 48 GB+ for longer sequences / higher rank
System RAM 32 GB 64 GB+ (needed for merge.device_map: cpu)
Disk ~120 GB (HF cache + checkpoints + merged weights) more if exporting GGUF

Training is interruption-safe: checkpoints are saved every epoch and qwench run resumes automatically.

Serving the result

  • transformers: qwench chat (uses merged weights if present, else base + adapter)
  • vLLM: point it at outputs/merged
  • Ollama: enable export, then run the ollama create ... command the export stage prints

Troubleshooting

Symptom Fix
CUDA out of memory during training Lower data.max_seq_len (biggest lever), then train.lora.r; keep per_device_train_batch_size: 1 and raise gradient_accumulation_steps instead
bitsandbytes missing / won't install Linux + CUDA only; on other platforms Qwench logs a warning and trains unquantized — fine for --smoke, not for the 27B
Training interrupted (crash, power, Ctrl-C) Just run qwench run again — it resumes from the last checkpoint of the same run; checkpoints from a different data/config are detected and cleared
Merge runs out of GPU memory Set merge.device_map: cpu to merge in system RAM (~60 GB for the 27B)
A stage keeps skipping but you want it re-run qwench run --force, or run the single stage: qwench train --force
Want to see what state things are in qwench status shows every stage's cache state

Development

make setup   # package + pytest only (no torch needed)
make test    # unit tests for the data stages — run without GPU or network

Project layout:

qwench/
├── configs/            pipeline.yaml (real run) · smoke.yaml (rehearsal)
│   └── presets/        dpo · kto · 48gb · small-model full FT
├── data/
│   ├── raw/            ← your training data goes here (raw-dpo/, raw-kto/ for those methods)
│   └── sample*/        bundled sample datasets (chat, preference, feedback)
├── qwench/
│   ├── cli.py          orchestrator (run/status/per-stage commands)
│   ├── config.py       typed config + YAML + --set overrides
│   ├── manifest.py     content-signature stage caching
│   ├── modeling.py     shared model/tokenizer/quantization loading
│   └── stages/         ingest · validate · prepare · train · evaluate · merge · export · serve
└── tests/              unit tests (pure Python, CPU-only)