| |
| |
| |
| """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") |
|
|
| |
| |
| 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 |
| |
|
|
| 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()) |
|
|