Primary target: a 7-9B student on a single RTX 3080 10GB (ADR-0010), with an Unsloth backend for roughly 2x speed and lower VRAM. Three strategies (QLoRA, full fine-tuning, knowledge distillation) and three backends (auto, trl, unsloth) compose freely; the dataset preparation is shared by all of them.
All training commands require the training extra; the device, dtype and quantization resolve automatically for the detected hardware:
uv sync --extra trainingFor Unsloth: uv sync --extra unsloth.
Run brainforge train check before a real run: it validates hardware, model
compatibility and the memory estimate without downloading weights (see
hardware reference).
uv run brainforge train prepare datasets/security_dataset.jsonl --output datasets/preparedWhat it does:
- Validates every record strictly and fails on the first invalid one (with index, id and reasons), so bad data never silently trains.
- Splits source-grouped 80/10/10, seeded (see dataset format).
- Exports
train.jsonl,validation.jsonl,test.jsonl,test_postcutoff.jsonlandstats.json.
Options: --train-ratio, --val-ratio, --seed, --output/-o.
| Strategy | What trains | Quantization | VRAM | Use when |
|---|---|---|---|---|
qlora (default) |
LoRA adapters | auto: 4-bit nf4 on CUDA/ROCm, none elsewhere |
~10GB on CUDA | first pass, fast iteration |
full |
every weight | none (bf16/fp16 per device) | model-size dependent | decision model after the dataset is proven |
Selected with training.strategy or train run --strategy {qlora,full}.
The LoRA rank/alpha/dropout come from config for qlora; full ignores
them. qlora with quantization none (the auto default off CUDA/ROCm)
trains plain LoRA.
| Backend | Extra | Notes |
|---|---|---|
auto (default) |
training |
resolves to trl (reserved for future selection logic) |
trl |
training |
Transformers + PEFT + bitsandbytes + TRL SFTTrainer |
unsloth |
unsloth |
patched kernels, ~2x speed; CUDA/ROCm only; 4bit or unquantized loads only (8bit fails fast) |
Selected with training.backend or train run --backend {auto,trl,unsloth}.
Both backends share dataset preparation, splits, resume and the
train_summary.json contract (which records the backend, strategy, device,
dtype, quantization and attention implementation actually used).
AirLLM is evaluated and not adopted (ADR-0017): layer streaming lowers VRAM but not for a 7-9B model that already fits, and its custom training API is incompatible with the PEFT/TRL, KD and GGUF contracts.
The agentic pack emits records that interleave assistant tool calls with
tool observations (see the dataset format). With the
default training.assistant_only_loss=false, the SFT loss scores every
completion-side token, tool observations included. Set it to true to mask
the loss to assistant turns: it requires a chat template with generation
markers, and TRL swaps in patched training templates for known families
(Qwen3, DeepSeek-V3) when tool calling is enabled. brainforge train run
warns when the training split holds tool turns or multi-assistant records
while the flag is off.
Two commands:
pipeline run --kdrecords the judge's per-token top-k logprobs into a<dataset>.kd.jsonlsidecar (requires a provider that serves logprobs; the mock provider produces word-level positions for dry runs).train run --kdtrains withalpha * T^2 * CE(student_T, teacher_topk_T) + (1 - alpha) * CE(gold).
Details and limits: knowledge distillation.
All knobs live in the training config section (defaults and semantics in
the configuration reference). The base model is
training.base_model (default Qwen/Qwen3-8B), never hardcoded; override
per run with --base-model.
| Field | Default | Meaning |
|---|---|---|
base_model |
Qwen/Qwen3-8B |
HF model id or local dir |
strategy |
qlora |
qlora or full |
backend |
auto |
auto (trl), trl or unsloth |
device |
auto |
auto, cuda, xpu, mps or cpu |
quantization |
auto |
auto, 4bit, 8bit, none (unsloth: 4bit or none; CUDA/ROCm only) |
lora_rank / lora_alpha / lora_dropout |
16 / 32 / 0.05 | LoRA shape (strategy qlora) |
learning_rate |
2e-4 | QLoRA sweet spot; full FT usually wants lower |
epochs |
3 | |
batch_size / gradient_accumulation |
1 / 16 | |
lr_scheduler_type |
cosine |
|
warmup_steps |
5 | |
max_grad_norm |
1.0 | |
max_length |
1024 | |
packing |
false | rejected for KD |
assistant_only_loss |
false | mask the loss to assistant turns (agentic multi-turn data) |
attn_implementation |
sdpa |
sdpa/flash_attention_2/eager |
eval_batch_size / generate_batch_size |
4 / 4 | |
save_steps |
100 | |
seed |
42 | |
output_dir |
experiments |
|
kd_alpha |
0.5 | KD loss weight |
kd_temperature |
2.0 | KD temperature |
kd_topk |
8 | teacher alternatives per token |
brainforge train run --dataset-dir datasets/prepared \
--strategy qlora --backend trl --epochs 3
brainforge train evaluate --model experiments/run-...
brainforge train task-eval --model experiments/run-... # security domain
brainforge train export --model experiments/run-... --format mergedtrain runwritesexperiments/run-<timestamp>/with the adapter, checkpoints andtrain_summary.json(which records the resolved device, dtype, quantization and effective strategy);--resumepicks the latest checkpoint numerically.train evaluatecomputes corpus perplexity on a split (eval.json);--quantization automatches the training-time quantization policy.train task-evalscores verdict/CWE accuracy against judge ground truth (security domain only);--quantization autoby default.train export --format mergedmerges the adapter into standalone HF weights.--format ggufconverts via llama.cpp to a quantized GGUF (defaultQ4_K_M,--quant) plus aModelfile, and with--ollama <name>registers the model with Ollama. llama.cpp is located via--llama-cpp-path, theLLAMA_CPP_DIRenv var, or PATH. See ship to Ollama.
- Primary signal:
test_postcutoff.jsonl, the cases teachers cannot have memorized. See contamination for why this is the headline metric. - Always compare base model vs fine-tuned student on identical prompts.
- The post-cutoff holdout stays the reference point across strategies; QLoRA-first then full-FT is a workflow, not a single number.
src/brainforge/training/smoke.py validates an accelerator environment with
one QLoRA forward/backward step on a tiny model:
uv sync --extra training
uv run python -m brainforge.training.smoke # skips cleanly without an accelerator
BRAINFORCE_SMOKE_MODEL=Qwen/Qwen3-0.6B ... # override the probe model
uv run python -m brainforge.training.smoke_cpu # CPU path (tiny random model)The manual gpu CI job runs this on a self-hosted runner
(CI reference).
Which devices train, how dtype and quantization resolve, and the preflight command: hardware reference.