betterwithage commited on
Commit
4addb8c
·
verified ·
1 Parent(s): 97f0182

code(atelier): receipted Unsloth v2 profiles — not a GPU retrain

Browse files
Files changed (1) hide show
  1. receipted_unsloth.py +245 -46
receipted_unsloth.py CHANGED
@@ -1,16 +1,125 @@
1
  #!/usr/bin/env python3
2
- # receipted_unsloth.py
3
- # Silhouette: Unsloth FastLanguageModel QLoRA.
4
- # Cut: dataset SHA, LoRA knobs, seed, and final loss go into a training receipt
5
- # BEFORE merge. GGUF is derived — never the signed object.
6
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7
  from __future__ import annotations
8
 
9
  import argparse
10
  import hashlib
11
  import json
 
12
  import time
13
  from pathlib import Path
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
14
 
15
 
16
  def sha256_file(p: Path) -> str:
@@ -21,72 +130,162 @@ def sha256_file(p: Path) -> str:
21
  return h.hexdigest()
22
 
23
 
24
- def main() -> None:
25
- ap = argparse.ArgumentParser()
26
- ap.add_argument("--base", default="Qwen/Qwen2.5-1.5B-Instruct")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
27
  ap.add_argument("--data", default="doctrine.jsonl")
28
  ap.add_argument("--out", default="out/adapter")
29
- ap.add_argument("--r", type=int, default=16)
30
- ap.add_argument("--seed", type=int, default=20260721)
31
- ap.add_argument("--max-seq", type=int, default=2048)
 
32
  args = ap.parse_args()
33
 
34
- data_sha = sha256_file(Path(args.data))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
35
  from unsloth import FastLanguageModel
36
  from datasets import load_dataset
37
  from trl import SFTConfig, SFTTrainer
38
 
39
  model, tokenizer = FastLanguageModel.from_pretrained(
40
- model_name=args.base,
41
- max_seq_length=args.max_seq,
42
- load_in_4bit=True,
 
43
  )
44
  model = FastLanguageModel.get_peft_model(
45
  model,
46
- r=args.r,
47
- lora_alpha=args.r,
48
- target_modules=["q_proj", "k_proj", "v_proj", "o_proj", "gate_proj", "up_proj", "down_proj"],
49
  lora_dropout=0,
50
  bias="none",
51
  use_gradient_checkpointing="unsloth",
52
- random_state=args.seed,
 
 
 
53
  )
54
- ds = load_dataset("json", data_files=args.data, split="train")
55
- trainer = SFTTrainer(
56
- model=model,
57
- tokenizer=tokenizer,
58
- train_dataset=ds,
59
- args=SFTConfig(
60
- output_dir=args.out,
61
- per_device_train_batch_size=2,
62
- gradient_accumulation_steps=4,
63
- max_steps=120,
64
- learning_rate=2e-4,
65
- logging_steps=10,
66
- seed=args.seed,
67
- ),
68
  )
 
 
 
 
 
 
 
69
  t0 = time.time()
70
  trainer.train()
 
71
  model.save_pretrained(args.out)
72
  loss = None
73
  if trainer.state.log_history:
74
  last = trainer.state.log_history[-1]
75
  loss = last.get("train_loss") or last.get("loss")
76
- receipt = {
77
- "schema": "szl.training_receipt.v1",
78
- "base": args.base,
79
- "dataset_sha256": data_sha,
80
- "lora": {"r": args.r, "alpha": args.r, "targets": "q,k,v,o,gate,up,down"},
81
- "seed": args.seed,
82
- "final_loss": loss,
83
- "seconds": round(time.time() - t0, 3),
84
- "note": "Sign this envelope (DSSE/Ed25519) BEFORE merge. GGUF is derived.",
85
- }
86
- Path(args.out).mkdir(parents=True, exist_ok=True)
87
- Path(args.out, "training_receipt.json").write_text(json.dumps(receipt, indent=2))
88
- print(json.dumps(receipt, indent=2))
89
 
90
 
91
  if __name__ == "__main__":
92
- main()
 
1
  #!/usr/bin/env python3
2
+ # SPDX-License-Identifier: Apache-2.0
3
+ # Copyright 2026 SZL Holdings
4
+ """Receipted Unsloth take the speed, bind the knobs, refuse the folklore.
 
5
 
6
+ Unsloth TAKE (HUB, not MEASURED here):
7
+ 4-bit QLoRA · fused QK-RoPE / SwiGLU Triton kernels · adamw_8bit
8
+ use_gradient_checkpointing="unsloth" · lora_dropout=0 · bias="none"
9
+ auto padding-free packing (1.1–2×, ~30% VRAM, loss comparable)
10
+
11
+ Unsloth LEAVE:
12
+ packing=True — Unsloth's own docs: changes the loss scale
13
+ LoftQ — start-of-run VRAM spike; not worth it on 0.5B–1.5B
14
+ MoE Split-LoRA — these organs are dense Qwen, not gpt-oss / Qwen3-MoE
15
+ GGUF as the signed object — derived. Always.
16
+ seed 3407 — house seed is 20260721
17
+ invented joules / 3× as MEASURED — their blog, not our receipt
18
+
19
+ SZL CUT (unique per organ, this file):
20
+ willay r=8 rsLoRA attn+mlp packing=false short ctx doctrine mouth
21
+ chaski r=8 rsLoRA attn-only packing=auto courier cannot author
22
+ chaski-5050 r=16 rsLoRA attn+mlp packing=auto mix is the identity
23
+ chaski-r2 r=8 rsLoRA attn-only packing=false lr=5e-5 R2 refinement; R1 stays
24
+ khipu r=16 attn+mlp packing=auto 4bit navigator, loss-comparable
25
+ receiptagent r=16 attn+mlp packing=false receipt-first SFT
26
+ khipu-r2 r=16 attn+mlp packing=false lineage, not overwrite
27
+
28
+ Every knob lands in training_receipt.json BEFORE merge.
29
+ Sign that envelope. Then merge. GGUF is derived.
30
+
31
+ python receipted_unsloth.py --profile willay --receipt-only
32
+ python receipted_unsloth.py --profile chaski --data doctrine.jsonl --out out/chaski
33
+ """
34
  from __future__ import annotations
35
 
36
  import argparse
37
  import hashlib
38
  import json
39
+ import sys
40
  import time
41
  from pathlib import Path
42
+ from typing import Any
43
+
44
+ SEED = 20260721
45
+ ATTN = ("q_proj", "k_proj", "v_proj", "o_proj")
46
+ ATTN_MLP = ATTN + ("gate_proj", "up_proj", "down_proj")
47
+
48
+ # Unique silhouettes. Rank/alpha keep alpha/r >= 1 (Unsloth hyperparameter guide).
49
+ # rsLoRA scales alpha/sqrt(r) — used on the small ranks so they do not vanish.
50
+ PROFILES: dict[str, dict[str, Any]] = {
51
+ "khipu": {
52
+ "base": "Qwen/Qwen2.5-1.5B-Instruct",
53
+ "r": 16, "alpha": 16, "rslora": False, "targets": ATTN_MLP,
54
+ "packing": "auto", "max_seq": 2048, "lr": 2e-4, "steps": 120,
55
+ "warmup": 10, "batch": 2, "accum": 4, "load_in_4bit": True,
56
+ "cut": "Navigator. 7 modules so the schema can be emitted. packing=auto keeps loss comparable to signed 0.0245. Not retrained here.",
57
+ },
58
+ "khipu-r2": {
59
+ "base": "Qwen/Qwen2.5-1.5B-Instruct",
60
+ "r": 16, "alpha": 16, "rslora": False, "targets": ATTN_MLP,
61
+ "packing": "false", "max_seq": 2048, "lr": 1e-4, "steps": 80,
62
+ "warmup": 8, "batch": 2, "accum": 4, "load_in_4bit": True,
63
+ "cut": "Lineage sibling. packing=false so R2 loss is comparable to R1. Do not overwrite R1.",
64
+ },
65
+ "receiptagent": {
66
+ "base": "Qwen/Qwen2.5-1.5B-Instruct",
67
+ "r": 16, "alpha": 16, "rslora": False, "targets": ATTN_MLP,
68
+ "packing": "false", "max_seq": 1536, "lr": 2e-4, "steps": 100,
69
+ "warmup": 10, "batch": 2, "accum": 4, "load_in_4bit": True,
70
+ "cut": "Receipt-first SFT. packing=false: the loss on the receipt tokens is the point.",
71
+ },
72
+ "willay": {
73
+ "base": "Qwen/Qwen2.5-0.5B-Instruct",
74
+ "r": 8, "alpha": 16, "rslora": True, "targets": ATTN_MLP,
75
+ "packing": "false", "max_seq": 1024, "lr": 1e-4, "steps": 160,
76
+ "warmup": 20, "batch": 2, "accum": 4, "load_in_4bit": True,
77
+ "cut": "Doctrine mouth. rsLoRA so rank-8 does not vanish. packing=false so silence/tell loss stays comparable. Short ctx: honesty set is short.",
78
+ },
79
+ "chaski": {
80
+ "base": "Qwen/Qwen3.5-0.8B",
81
+ "r": 8, "alpha": 16, "rslora": True, "targets": ATTN,
82
+ "packing": "auto", "max_seq": 1536, "lr": 1e-4, "steps": 120,
83
+ "warmup": 12, "batch": 2, "accum": 4, "load_in_4bit": True,
84
+ "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).",
85
+ },
86
+ "chaski-5050": {
87
+ "base": "Qwen/Qwen3.5-0.8B",
88
+ "r": 16, "alpha": 16, "rslora": True, "targets": ATTN_MLP,
89
+ "packing": "auto", "max_seq": 1536, "lr": 1e-4, "steps": 120,
90
+ "warmup": 12, "batch": 2, "accum": 4, "load_in_4bit": True,
91
+ "cut": "50/50 cutting mix. Extra MLP rank so the courier is allowed to STOP. Mix is the identity.",
92
+ },
93
+ "chaski-r2": {
94
+ "base": "Qwen/Qwen3.5-0.8B",
95
+ "r": 8, "alpha": 16, "rslora": True, "targets": ATTN,
96
+ "packing": "false", "max_seq": 1536, "lr": 5e-5, "steps": 80,
97
+ "warmup": 8, "batch": 2, "accum": 4, "load_in_4bit": True,
98
+ "cut": "Round-2 refinement. packing=false + lower lr. R1 stays up. Do not overwrite.",
99
+ },
100
+ }
101
+
102
+ TECHNIQUES = {
103
+ "take": [
104
+ "4-bit QLoRA (load_in_4bit)",
105
+ "fused QK-RoPE + SwiGLU Triton kernels (automatic in FastLanguageModel)",
106
+ "adamw_8bit",
107
+ "use_gradient_checkpointing='unsloth'",
108
+ "lora_dropout=0, bias='none' (Unsloth-optimized)",
109
+ "auto padding-free packing when packing=auto",
110
+ "rsLoRA (alpha/sqrt(r)) on small-rank organs",
111
+ ],
112
+ "leave": [
113
+ "packing=True (changes loss scale — Unsloth docs)",
114
+ "LoftQ (start-of-run VRAM spike)",
115
+ "MoE Split-LoRA (not a MoE organ)",
116
+ "GGUF as the signed object",
117
+ "seed 3407",
118
+ "tokens-per-joule invented",
119
+ "3×/5× speed cited as MEASURED — that is Unsloth's HUB claim",
120
+ ],
121
+ "cut": "Every knob in the receipt. Unique rank/targets/packing per organ. House seed 20260721. Sign before merge.",
122
+ }
123
 
124
 
125
  def sha256_file(p: Path) -> str:
 
130
  return h.hexdigest()
131
 
132
 
133
+ def effective_scale(alpha: int, r: int, rslora: bool) -> float:
134
+ return (alpha / (r ** 0.5)) if rslora else (alpha / r)
135
+
136
+
137
+ def mint_receipt(profile: str, cfg: dict[str, Any], extra: dict[str, Any]) -> dict[str, Any]:
138
+ rec = {
139
+ "schema": "szl.training_receipt.v2",
140
+ "profile": profile,
141
+ "base": cfg["base"],
142
+ "cut": cfg["cut"],
143
+ "lora": {
144
+ "r": cfg["r"],
145
+ "alpha": cfg["alpha"],
146
+ "rslora": cfg["rslora"],
147
+ "effective_scale": round(effective_scale(cfg["alpha"], cfg["r"], cfg["rslora"]), 6),
148
+ "targets": list(cfg["targets"]),
149
+ "dropout": 0,
150
+ "bias": "none",
151
+ "loftq": False,
152
+ },
153
+ "unsloth": {
154
+ "load_in_4bit": cfg["load_in_4bit"],
155
+ "gradient_checkpointing": "unsloth",
156
+ "optim": "adamw_8bit",
157
+ "packing": cfg["packing"],
158
+ "max_seq": cfg["max_seq"],
159
+ "lr": cfg["lr"],
160
+ "max_steps": cfg["steps"],
161
+ "warmup": cfg["warmup"],
162
+ "batch": cfg["batch"],
163
+ "grad_accum": cfg["accum"],
164
+ "techniques": TECHNIQUES,
165
+ },
166
+ "seed": SEED,
167
+ "proven_trust": False,
168
+ "energy_j": None,
169
+ "energy_status": "UNAVAILABLE",
170
+ "gguf": "derived — never the signed object",
171
+ "note": "Sign this envelope (DSSE/Ed25519) BEFORE merge. Do not GPU-retrain 1.5B from this atelier.",
172
+ }
173
+ rec.update(extra)
174
+ if rec.get("proven_trust") is True:
175
+ raise ValueError("refusing proven_trust true")
176
+ if rec.get("energy_j") not in (None,):
177
+ raise ValueError("refusing to fabricate joules")
178
+ return rec
179
+
180
+
181
+ def main() -> int:
182
+ ap = argparse.ArgumentParser(description="Receipted Unsloth. Unique cut per organ.")
183
+ ap.add_argument("--profile", choices=sorted(PROFILES), default="khipu")
184
  ap.add_argument("--data", default="doctrine.jsonl")
185
  ap.add_argument("--out", default="out/adapter")
186
+ ap.add_argument("--receipt-only", action="store_true", help="Mint the recipe receipt. No GPU. No fit.")
187
+ ap.add_argument("--list", action="store_true")
188
+ ap.add_argument("--packing", choices=["auto", "true", "false"], default=None)
189
+ ap.add_argument("--i-accept-incomparable-loss", action="store_true")
190
  args = ap.parse_args()
191
 
192
+ if args.list:
193
+ for name, cfg in PROFILES.items():
194
+ print(f"{name:14} r={cfg['r']:<3} rsLoRA={str(cfg['rslora']):5} pack={cfg['packing']:5} "
195
+ f"tgt={len(cfg['targets'])} {cfg['base']}")
196
+ print(f" {cfg['cut']}")
197
+ return 0
198
+
199
+ cfg = dict(PROFILES[args.profile])
200
+ if args.packing:
201
+ cfg["packing"] = args.packing
202
+ if cfg["packing"] == "true" and not args.i_accept_incomparable_loss:
203
+ print("refusing packing=true: Unsloth docs say it changes the loss scale. "
204
+ "Pass --i-accept-incomparable-loss if you still want it.", file=sys.stderr)
205
+ return 2
206
+
207
+ data_path = Path(args.data)
208
+ extra: dict[str, Any] = {
209
+ "dataset": str(data_path),
210
+ "dataset_sha256": sha256_file(data_path) if data_path.exists() else None,
211
+ "dataset_status": "MEASURED" if data_path.exists() else "UNAVAILABLE",
212
+ "honesty": "RECIPE" if args.receipt_only else "REPORTED",
213
+ }
214
+
215
+ if args.receipt_only:
216
+ rec = mint_receipt(args.profile, cfg, extra)
217
+ out = Path(args.out)
218
+ out.mkdir(parents=True, exist_ok=True)
219
+ (out / "training_receipt.json").write_text(json.dumps(rec, indent=2) + "\n")
220
+ print(json.dumps(rec, indent=2))
221
+ return 0
222
+
223
+ if not data_path.exists():
224
+ print(f"missing dataset {data_path} — pass --receipt-only to mint the recipe without GPU", file=sys.stderr)
225
+ return 2
226
+
227
+ extra["dataset_sha256"] = sha256_file(data_path)
228
+ extra["dataset_status"] = "MEASURED"
229
+
230
  from unsloth import FastLanguageModel
231
  from datasets import load_dataset
232
  from trl import SFTConfig, SFTTrainer
233
 
234
  model, tokenizer = FastLanguageModel.from_pretrained(
235
+ model_name=cfg["base"],
236
+ max_seq_length=cfg["max_seq"],
237
+ load_in_4bit=cfg["load_in_4bit"],
238
+ full_finetuning=False,
239
  )
240
  model = FastLanguageModel.get_peft_model(
241
  model,
242
+ r=cfg["r"],
243
+ lora_alpha=cfg["alpha"],
244
+ target_modules=list(cfg["targets"]),
245
  lora_dropout=0,
246
  bias="none",
247
  use_gradient_checkpointing="unsloth",
248
+ random_state=SEED,
249
+ use_rslora=cfg["rslora"],
250
+ loftq_config=None,
251
+ max_seq_length=cfg["max_seq"],
252
  )
253
+ ds = load_dataset("json", data_files=str(data_path), split="train")
254
+ sft_kw: dict[str, Any] = dict(
255
+ output_dir=args.out,
256
+ per_device_train_batch_size=cfg["batch"],
257
+ gradient_accumulation_steps=cfg["accum"],
258
+ max_steps=cfg["steps"],
259
+ learning_rate=cfg["lr"],
260
+ warmup_steps=cfg["warmup"],
261
+ logging_steps=10,
262
+ seed=SEED,
263
+ optim="adamw_8bit",
264
+ max_seq_length=cfg["max_seq"],
265
+ lr_scheduler_type="cosine",
266
+ weight_decay=0.01,
267
  )
268
+ if cfg["packing"] == "true":
269
+ sft_kw["packing"] = True
270
+ elif cfg["packing"] == "false":
271
+ sft_kw["packing"] = False
272
+ # packing=auto: omit the flag — Unsloth padding-free default, loss comparable.
273
+
274
+ trainer = SFTTrainer(model=model, tokenizer=tokenizer, train_dataset=ds, args=SFTConfig(**sft_kw))
275
  t0 = time.time()
276
  trainer.train()
277
+ Path(args.out).mkdir(parents=True, exist_ok=True)
278
  model.save_pretrained(args.out)
279
  loss = None
280
  if trainer.state.log_history:
281
  last = trainer.state.log_history[-1]
282
  loss = last.get("train_loss") or last.get("loss")
283
+ extra.update({"final_loss": loss, "seconds": round(time.time() - t0, 3), "honesty": "REPORTED"})
284
+ rec = mint_receipt(args.profile, cfg, extra)
285
+ Path(args.out, "training_receipt.json").write_text(json.dumps(rec, indent=2) + "\n")
286
+ print(json.dumps(rec, indent=2))
287
+ return 0
 
 
 
 
 
 
 
 
288
 
289
 
290
  if __name__ == "__main__":
291
+ raise SystemExit(main())