szl-training-scripts / receipted_unsloth.py
betterwithage's picture
code(atelier): receipted Unsloth v2 profiles — not a GPU retrain
4addb8c verified
Raw
History Blame Contribute Delete
12.2 kB
#!/usr/bin/env python3
# SPDX-License-Identifier: Apache-2.0
# Copyright 2026 SZL Holdings
"""Receipted Unsloth — take the speed, bind the knobs, refuse the folklore.
Unsloth TAKE (HUB, not MEASURED here):
4-bit QLoRA · fused QK-RoPE / SwiGLU Triton kernels · adamw_8bit
use_gradient_checkpointing="unsloth" · lora_dropout=0 · bias="none"
auto padding-free packing (1.1–2×, ~30% VRAM, loss comparable)
Unsloth LEAVE:
packing=True — Unsloth's own docs: changes the loss scale
LoftQ — start-of-run VRAM spike; not worth it on 0.5B–1.5B
MoE Split-LoRA — these organs are dense Qwen, not gpt-oss / Qwen3-MoE
GGUF as the signed object — derived. Always.
seed 3407 — house seed is 20260721
invented joules / 3× as MEASURED — their blog, not our receipt
SZL CUT (unique per organ, this file):
willay r=8 rsLoRA attn+mlp packing=false short ctx doctrine mouth
chaski r=8 rsLoRA attn-only packing=auto courier cannot author
chaski-5050 r=16 rsLoRA attn+mlp packing=auto mix is the identity
chaski-r2 r=8 rsLoRA attn-only packing=false lr=5e-5 R2 refinement; R1 stays
khipu r=16 attn+mlp packing=auto 4bit navigator, loss-comparable
receiptagent r=16 attn+mlp packing=false receipt-first SFT
khipu-r2 r=16 attn+mlp packing=false lineage, not overwrite
Every knob lands in training_receipt.json BEFORE merge.
Sign that envelope. Then merge. GGUF is derived.
python receipted_unsloth.py --profile willay --receipt-only
python receipted_unsloth.py --profile chaski --data doctrine.jsonl --out out/chaski
"""
from __future__ import annotations
import argparse
import hashlib
import json
import sys
import time
from pathlib import Path
from typing import Any
SEED = 20260721
ATTN = ("q_proj", "k_proj", "v_proj", "o_proj")
ATTN_MLP = ATTN + ("gate_proj", "up_proj", "down_proj")
# Unique silhouettes. Rank/alpha keep alpha/r >= 1 (Unsloth hyperparameter guide).
# rsLoRA scales alpha/sqrt(r) — used on the small ranks so they do not vanish.
PROFILES: dict[str, dict[str, Any]] = {
"khipu": {
"base": "Qwen/Qwen2.5-1.5B-Instruct",
"r": 16, "alpha": 16, "rslora": False, "targets": ATTN_MLP,
"packing": "auto", "max_seq": 2048, "lr": 2e-4, "steps": 120,
"warmup": 10, "batch": 2, "accum": 4, "load_in_4bit": True,
"cut": "Navigator. 7 modules so the schema can be emitted. packing=auto keeps loss comparable to signed 0.0245. Not retrained here.",
},
"khipu-r2": {
"base": "Qwen/Qwen2.5-1.5B-Instruct",
"r": 16, "alpha": 16, "rslora": False, "targets": ATTN_MLP,
"packing": "false", "max_seq": 2048, "lr": 1e-4, "steps": 80,
"warmup": 8, "batch": 2, "accum": 4, "load_in_4bit": True,
"cut": "Lineage sibling. packing=false so R2 loss is comparable to R1. Do not overwrite R1.",
},
"receiptagent": {
"base": "Qwen/Qwen2.5-1.5B-Instruct",
"r": 16, "alpha": 16, "rslora": False, "targets": ATTN_MLP,
"packing": "false", "max_seq": 1536, "lr": 2e-4, "steps": 100,
"warmup": 10, "batch": 2, "accum": 4, "load_in_4bit": True,
"cut": "Receipt-first SFT. packing=false: the loss on the receipt tokens is the point.",
},
"willay": {
"base": "Qwen/Qwen2.5-0.5B-Instruct",
"r": 8, "alpha": 16, "rslora": True, "targets": ATTN_MLP,
"packing": "false", "max_seq": 1024, "lr": 1e-4, "steps": 160,
"warmup": 20, "batch": 2, "accum": 4, "load_in_4bit": True,
"cut": "Doctrine mouth. rsLoRA so rank-8 does not vanish. packing=false so silence/tell loss stays comparable. Short ctx: honesty set is short.",
},
"chaski": {
"base": "Qwen/Qwen3.5-0.8B",
"r": 8, "alpha": 16, "rslora": True, "targets": ATTN,
"packing": "auto", "max_seq": 1536, "lr": 1e-4, "steps": 120,
"warmup": 12, "batch": 2, "accum": 4, "load_in_4bit": True,
"cut": "Courier. Attention-only LoRA — MLP stays frozen so the runner cannot author the payload. Unique cut. Dense 0.8B, 4bit is allowed (MoE QLoRA is the thing Unsloth warns against).",
},
"chaski-5050": {
"base": "Qwen/Qwen3.5-0.8B",
"r": 16, "alpha": 16, "rslora": True, "targets": ATTN_MLP,
"packing": "auto", "max_seq": 1536, "lr": 1e-4, "steps": 120,
"warmup": 12, "batch": 2, "accum": 4, "load_in_4bit": True,
"cut": "50/50 cutting mix. Extra MLP rank so the courier is allowed to STOP. Mix is the identity.",
},
"chaski-r2": {
"base": "Qwen/Qwen3.5-0.8B",
"r": 8, "alpha": 16, "rslora": True, "targets": ATTN,
"packing": "false", "max_seq": 1536, "lr": 5e-5, "steps": 80,
"warmup": 8, "batch": 2, "accum": 4, "load_in_4bit": True,
"cut": "Round-2 refinement. packing=false + lower lr. R1 stays up. Do not overwrite.",
},
}
TECHNIQUES = {
"take": [
"4-bit QLoRA (load_in_4bit)",
"fused QK-RoPE + SwiGLU Triton kernels (automatic in FastLanguageModel)",
"adamw_8bit",
"use_gradient_checkpointing='unsloth'",
"lora_dropout=0, bias='none' (Unsloth-optimized)",
"auto padding-free packing when packing=auto",
"rsLoRA (alpha/sqrt(r)) on small-rank organs",
],
"leave": [
"packing=True (changes loss scale — Unsloth docs)",
"LoftQ (start-of-run VRAM spike)",
"MoE Split-LoRA (not a MoE organ)",
"GGUF as the signed object",
"seed 3407",
"tokens-per-joule invented",
"3×/5× speed cited as MEASURED — that is Unsloth's HUB claim",
],
"cut": "Every knob in the receipt. Unique rank/targets/packing per organ. House seed 20260721. Sign before merge.",
}
def sha256_file(p: Path) -> str:
h = hashlib.sha256()
with p.open("rb") as f:
for chunk in iter(lambda: f.read(1 << 20), b""):
h.update(chunk)
return h.hexdigest()
def effective_scale(alpha: int, r: int, rslora: bool) -> float:
return (alpha / (r ** 0.5)) if rslora else (alpha / r)
def mint_receipt(profile: str, cfg: dict[str, Any], extra: dict[str, Any]) -> dict[str, Any]:
rec = {
"schema": "szl.training_receipt.v2",
"profile": profile,
"base": cfg["base"],
"cut": cfg["cut"],
"lora": {
"r": cfg["r"],
"alpha": cfg["alpha"],
"rslora": cfg["rslora"],
"effective_scale": round(effective_scale(cfg["alpha"], cfg["r"], cfg["rslora"]), 6),
"targets": list(cfg["targets"]),
"dropout": 0,
"bias": "none",
"loftq": False,
},
"unsloth": {
"load_in_4bit": cfg["load_in_4bit"],
"gradient_checkpointing": "unsloth",
"optim": "adamw_8bit",
"packing": cfg["packing"],
"max_seq": cfg["max_seq"],
"lr": cfg["lr"],
"max_steps": cfg["steps"],
"warmup": cfg["warmup"],
"batch": cfg["batch"],
"grad_accum": cfg["accum"],
"techniques": TECHNIQUES,
},
"seed": SEED,
"proven_trust": False,
"energy_j": None,
"energy_status": "UNAVAILABLE",
"gguf": "derived — never the signed object",
"note": "Sign this envelope (DSSE/Ed25519) BEFORE merge. Do not GPU-retrain 1.5B from this atelier.",
}
rec.update(extra)
if rec.get("proven_trust") is True:
raise ValueError("refusing proven_trust true")
if rec.get("energy_j") not in (None,):
raise ValueError("refusing to fabricate joules")
return rec
def main() -> int:
ap = argparse.ArgumentParser(description="Receipted Unsloth. Unique cut per organ.")
ap.add_argument("--profile", choices=sorted(PROFILES), default="khipu")
ap.add_argument("--data", default="doctrine.jsonl")
ap.add_argument("--out", default="out/adapter")
ap.add_argument("--receipt-only", action="store_true", help="Mint the recipe receipt. No GPU. No fit.")
ap.add_argument("--list", action="store_true")
ap.add_argument("--packing", choices=["auto", "true", "false"], default=None)
ap.add_argument("--i-accept-incomparable-loss", action="store_true")
args = ap.parse_args()
if args.list:
for name, cfg in PROFILES.items():
print(f"{name:14} r={cfg['r']:<3} rsLoRA={str(cfg['rslora']):5} pack={cfg['packing']:5} "
f"tgt={len(cfg['targets'])} {cfg['base']}")
print(f" {cfg['cut']}")
return 0
cfg = dict(PROFILES[args.profile])
if args.packing:
cfg["packing"] = args.packing
if cfg["packing"] == "true" and not args.i_accept_incomparable_loss:
print("refusing packing=true: Unsloth docs say it changes the loss scale. "
"Pass --i-accept-incomparable-loss if you still want it.", file=sys.stderr)
return 2
data_path = Path(args.data)
extra: dict[str, Any] = {
"dataset": str(data_path),
"dataset_sha256": sha256_file(data_path) if data_path.exists() else None,
"dataset_status": "MEASURED" if data_path.exists() else "UNAVAILABLE",
"honesty": "RECIPE" if args.receipt_only else "REPORTED",
}
if args.receipt_only:
rec = mint_receipt(args.profile, cfg, extra)
out = Path(args.out)
out.mkdir(parents=True, exist_ok=True)
(out / "training_receipt.json").write_text(json.dumps(rec, indent=2) + "\n")
print(json.dumps(rec, indent=2))
return 0
if not data_path.exists():
print(f"missing dataset {data_path} — pass --receipt-only to mint the recipe without GPU", file=sys.stderr)
return 2
extra["dataset_sha256"] = sha256_file(data_path)
extra["dataset_status"] = "MEASURED"
from unsloth import FastLanguageModel
from datasets import load_dataset
from trl import SFTConfig, SFTTrainer
model, tokenizer = FastLanguageModel.from_pretrained(
model_name=cfg["base"],
max_seq_length=cfg["max_seq"],
load_in_4bit=cfg["load_in_4bit"],
full_finetuning=False,
)
model = FastLanguageModel.get_peft_model(
model,
r=cfg["r"],
lora_alpha=cfg["alpha"],
target_modules=list(cfg["targets"]),
lora_dropout=0,
bias="none",
use_gradient_checkpointing="unsloth",
random_state=SEED,
use_rslora=cfg["rslora"],
loftq_config=None,
max_seq_length=cfg["max_seq"],
)
ds = load_dataset("json", data_files=str(data_path), split="train")
sft_kw: dict[str, Any] = dict(
output_dir=args.out,
per_device_train_batch_size=cfg["batch"],
gradient_accumulation_steps=cfg["accum"],
max_steps=cfg["steps"],
learning_rate=cfg["lr"],
warmup_steps=cfg["warmup"],
logging_steps=10,
seed=SEED,
optim="adamw_8bit",
max_seq_length=cfg["max_seq"],
lr_scheduler_type="cosine",
weight_decay=0.01,
)
if cfg["packing"] == "true":
sft_kw["packing"] = True
elif cfg["packing"] == "false":
sft_kw["packing"] = False
# packing=auto: omit the flag — Unsloth padding-free default, loss comparable.
trainer = SFTTrainer(model=model, tokenizer=tokenizer, train_dataset=ds, args=SFTConfig(**sft_kw))
t0 = time.time()
trainer.train()
Path(args.out).mkdir(parents=True, exist_ok=True)
model.save_pretrained(args.out)
loss = None
if trainer.state.log_history:
last = trainer.state.log_history[-1]
loss = last.get("train_loss") or last.get("loss")
extra.update({"final_loss": loss, "seconds": round(time.time() - t0, 3), "honesty": "REPORTED"})
rec = mint_receipt(args.profile, cfg, extra)
Path(args.out, "training_receipt.json").write_text(json.dumps(rec, indent=2) + "\n")
print(json.dumps(rec, indent=2))
return 0
if __name__ == "__main__":
raise SystemExit(main())