push from SNAPKITTYWEST/summon
Browse files- README.md +175 -0
- examples/build_my_model.py +64 -0
- setup.py +35 -0
- summon/__init__.py +41 -0
- summon/corpus/__init__.py +1 -0
- summon/corpus/builder.py +122 -0
- summon/identity/__init__.py +1 -0
- summon/identity/model.py +187 -0
- summon/train/__init__.py +1 -0
- summon/train/runner.py +151 -0
README.md
ADDED
|
@@ -0,0 +1,175 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
```
|
| 2 |
+
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 3 |
+
β β
|
| 4 |
+
β ⬑ S U M M O N β
|
| 5 |
+
β β
|
| 6 |
+
β ββββββββββββββββββββββββββββββββββββββββ β
|
| 7 |
+
β Build your own weights. Name your own model. β
|
| 8 |
+
β Sovereign fine-tuning framework Β· pip install summon β
|
| 9 |
+
β ββββββββββββββββββββββββββββββββββββββββ β
|
| 10 |
+
β β
|
| 11 |
+
β ⬑ Ξ© βΊ Ξ¨ Ξ Ξ Ξ£ Ξ¦ Ξ± β WORM SEALED AT EVERY STEP β
|
| 12 |
+
β β
|
| 13 |
+
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 14 |
+
```
|
| 15 |
+
|
| 16 |
+
Summon is a pip-installable Python framework for building sovereign fine-tuned language models. It wraps HuggingFace PEFT + TRL + bitsandbytes into a single fluent chain: `Summon.begin("YourModel")` β `.base()` β `.corpus()` β `.constitutional()` β `.license()` β `.train()` β `.push()`. The API is designed in the functional / immutable style β every method returns a new `SovereignModel` instance; nothing mutates in place. The `Corpus` builder is equally composable: stack named layers from JSONL files or raw string lists, then `.seal()` to freeze them. A SHA-256 WORM chain runs through every step of both pipelines, producing a verifiable manifest of exactly what data, what base, and what constitution went into your weights. Training is QLoRA (4-bit NF4 quantization via bitsandbytes, `r=16 lora_alpha=32`, target modules `q_proj/v_proj/k_proj/o_proj`) using HuggingFace `SFTTrainer`. Supported base models include Nemotron Mini 4B, Llama 3 8B/70B, Mistral 7B, Phi-3 Mini, Qwen2 7B, Gemma2 9B, and Falcon 7B β or pass any HuggingFace ID directly.
|
| 17 |
+
|
| 18 |
+
## Architecture
|
| 19 |
+
|
| 20 |
+
```mermaid
|
| 21 |
+
flowchart LR
|
| 22 |
+
subgraph Corpus Builder
|
| 23 |
+
C0([Corpus.layer 0\ngenesis.jsonl])
|
| 24 |
+
C1([Corpus.layer 1\nenochian.jsonl])
|
| 25 |
+
C2([Corpus.layer N\n...])
|
| 26 |
+
CS([.seal\nWORM hash])
|
| 27 |
+
C0 --> C1 --> C2 --> CS
|
| 28 |
+
end
|
| 29 |
+
|
| 30 |
+
subgraph SovereignModel Chain
|
| 31 |
+
M0([Summon.begin\nname]) --> M1
|
| 32 |
+
M1([.base\nnominate HF model]) --> M2
|
| 33 |
+
M2([.corpus\nlayers / Corpus obj]) --> M3
|
| 34 |
+
M3([.constitutional\nprinciples list]) --> M4
|
| 35 |
+
M4([.license\nsovereign-source-v1]) --> M5
|
| 36 |
+
M5([.train\nQLoRA 4-bit]) --> M6
|
| 37 |
+
M6([.push\nHuggingFace Hub])
|
| 38 |
+
end
|
| 39 |
+
|
| 40 |
+
CS -->|corpus_obj| M2
|
| 41 |
+
|
| 42 |
+
subgraph Trainer
|
| 43 |
+
T0[Load base model\nBitsAndBytesConfig NF4]
|
| 44 |
+
T1[Apply LoraConfig\nr=16 alpha=32]
|
| 45 |
+
T2[SFTTrainer\nepochs Β· batch Β· lr]
|
| 46 |
+
T3[save_model\nwrite model card]
|
| 47 |
+
T0 --> T1 --> T2 --> T3
|
| 48 |
+
end
|
| 49 |
+
|
| 50 |
+
M5 --> Trainer
|
| 51 |
+
|
| 52 |
+
subgraph WORM Chain
|
| 53 |
+
W0[GENESIS] --> W1[BASE seal]
|
| 54 |
+
W1 --> W2[CORPUS seal]
|
| 55 |
+
W2 --> W3[CONSTITUTION seal]
|
| 56 |
+
W3 --> W4[LICENSE seal]
|
| 57 |
+
W4 --> W5[TRAIN seal]
|
| 58 |
+
W5 --> W6[PUSH seal]
|
| 59 |
+
end
|
| 60 |
+
|
| 61 |
+
M1 -.->|SHA-256| W1
|
| 62 |
+
M2 -.->|SHA-256| W2
|
| 63 |
+
M3 -.->|SHA-256| W3
|
| 64 |
+
M4 -.->|SHA-256| W4
|
| 65 |
+
M5 -.->|SHA-256| W5
|
| 66 |
+
M6 -.->|SHA-256| W6
|
| 67 |
+
```
|
| 68 |
+
|
| 69 |
+
## File Tree
|
| 70 |
+
|
| 71 |
+
```
|
| 72 |
+
summon/
|
| 73 |
+
βββ summon/
|
| 74 |
+
β βββ __init__.py # Summon class β .begin() and .corpus() entry points
|
| 75 |
+
β βββ identity/
|
| 76 |
+
β β βββ __init__.py
|
| 77 |
+
β β βββ model.py # SovereignModel β fluent chain (.base/.corpus/.constitutional/.license/.train/.push/.manifest)
|
| 78 |
+
β βββ corpus/
|
| 79 |
+
β β βββ __init__.py
|
| 80 |
+
β β βββ builder.py # Corpus β layered JSONL builder (.layer/.layer_raw/.seal/.export/.summary)
|
| 81 |
+
β βββ train/
|
| 82 |
+
β βββ __init__.py
|
| 83 |
+
β βββ runner.py # Trainer β QLoRA fine-tuning (validate/run/_write_model_card)
|
| 84 |
+
βββ examples/
|
| 85 |
+
β βββ build_my_model.py # Three usage patterns: full pipeline, corpus-first, dry run
|
| 86 |
+
βββ setup.py # pip packaging (extras: [train] and [hub])
|
| 87 |
+
βββ README.md
|
| 88 |
+
```
|
| 89 |
+
|
| 90 |
+
## Quick Start
|
| 91 |
+
|
| 92 |
+
```bash
|
| 93 |
+
# Install (core β no GPU deps)
|
| 94 |
+
pip install summon
|
| 95 |
+
|
| 96 |
+
# Install with training deps
|
| 97 |
+
pip install "summon[train]" # torch, transformers, peft, trl, datasets, bitsandbytes, accelerate
|
| 98 |
+
|
| 99 |
+
# Install with HuggingFace Hub push support
|
| 100 |
+
pip install "summon[train,hub]"
|
| 101 |
+
```
|
| 102 |
+
|
| 103 |
+
**Full pipeline:**
|
| 104 |
+
|
| 105 |
+
```python
|
| 106 |
+
from summon import Summon
|
| 107 |
+
|
| 108 |
+
model = (
|
| 109 |
+
Summon.begin("AhmadMeta-v1")
|
| 110 |
+
.base("nemotron-mini-4b") # or full HF ID: "nvidia/Minitron-4B-Base"
|
| 111 |
+
.corpus(layers=[
|
| 112 |
+
"data/the_book.jsonl",
|
| 113 |
+
"data/enoch.jsonl",
|
| 114 |
+
"data/circle7.jsonl",
|
| 115 |
+
])
|
| 116 |
+
.constitutional(["truth", "sovereignty", "evidence", "no_deception"])
|
| 117 |
+
.license("sovereign-source-v1")
|
| 118 |
+
.train(device="cuda", epochs=3, batch_size=4, learning_rate=2e-4)
|
| 119 |
+
.push("my-org/AhmadMeta-v1")
|
| 120 |
+
)
|
| 121 |
+
|
| 122 |
+
model.manifest() # print WORM-sealed manifest, optionally write to file
|
| 123 |
+
```
|
| 124 |
+
|
| 125 |
+
**Corpus builder separately:**
|
| 126 |
+
|
| 127 |
+
```python
|
| 128 |
+
from summon import Summon
|
| 129 |
+
|
| 130 |
+
corpus = (
|
| 131 |
+
Summon.corpus()
|
| 132 |
+
.layer(0, "data/genesis.jsonl", name="genesis")
|
| 133 |
+
.layer(1, "data/enoch.jsonl", name="enochian")
|
| 134 |
+
.layer_raw(2, ["raw text line 1", "raw text line 2"], name="inline")
|
| 135 |
+
.seal()
|
| 136 |
+
)
|
| 137 |
+
|
| 138 |
+
model = (
|
| 139 |
+
Summon.begin("JessicaLM-v1")
|
| 140 |
+
.base("llama3-8b")
|
| 141 |
+
.corpus(corpus_obj=corpus) # pass the sealed Corpus object
|
| 142 |
+
.constitutional(["truth", "care", "sovereignty"])
|
| 143 |
+
.license("apache-2.0")
|
| 144 |
+
.train(device="cuda", epochs=5)
|
| 145 |
+
.push("jessica-org/JessicaLM-v1")
|
| 146 |
+
)
|
| 147 |
+
```
|
| 148 |
+
|
| 149 |
+
**Dry run (validate config, no GPU):**
|
| 150 |
+
|
| 151 |
+
```python
|
| 152 |
+
model = (
|
| 153 |
+
Summon.begin("TestModel-v1")
|
| 154 |
+
.base("phi3-mini")
|
| 155 |
+
.corpus(layers=["data/sample.jsonl"])
|
| 156 |
+
.constitutional(["truth"])
|
| 157 |
+
.train(dry_run=True) # validates, does not launch training
|
| 158 |
+
)
|
| 159 |
+
```
|
| 160 |
+
|
| 161 |
+
## Key Features
|
| 162 |
+
|
| 163 |
+
- **Single fluent chain** β `Summon.begin("name").base().corpus().constitutional().license().train().push()` β the entire pipeline in one expression
|
| 164 |
+
- **Functional / immutable API** β every method returns a new `SovereignModel` instance; state never mutates; safe to branch at any point
|
| 165 |
+
- **Layered Corpus builder** β stack named JSONL layers with `.layer()` (from file) or `.layer_raw()` (from string list); `.seal()` freezes and WORM-stamps the corpus; `.export()` merges all layers to a single JSONL for training
|
| 166 |
+
- **QLoRA fine-tuning** β 4-bit NF4 quantization via bitsandbytes, LoRA rank 16, alpha 32, targets `q_proj/v_proj/k_proj/o_proj`, `SFTTrainer` with gradient accumulation and fp16; auto-writes a HuggingFace model card with WORM seal
|
| 167 |
+
- **Constitutional principles** β `.constitutional(["truth","sovereignty","evidence"])` bakes your principles into the model manifest and model card; shapes RLHF/preference data generation
|
| 168 |
+
- **Eight supported base models** β Nemotron Mini 4B, Llama 3 8B/70B, Mistral 7B, Phi-3 Mini, Qwen2 7B, Gemma2 9B, Falcon 7B β or pass any HuggingFace model ID directly
|
| 169 |
+
- **WORM chain on every step** β SHA-256 hash chain from `GENESIS` through `BASE β CORPUS β CONSTITUTION β LICENSE β TRAIN β PUSH`; final hash in `.manifest()` proves exact provenance
|
| 170 |
+
- **`.manifest()` output** β prints and optionally writes a JSON document with name, base, corpus layers, constitution, license, output path, WORM head hash, and creation timestamp
|
| 171 |
+
- **HuggingFace Hub push** β `.push("org/model")` calls `HfApi().upload_folder()` with optional `private=True`; warns if `.train()` was not called first
|
| 172 |
+
|
| 173 |
+
---
|
| 174 |
+
|
| 175 |
+
*Apache 2.0 Β· SnapKitty Collective 2026 Β· Evidence or Silence*
|
examples/build_my_model.py
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Example: Build your own sovereign model with Summon
|
| 2 |
+
# pip install summon
|
| 3 |
+
# python examples/build_my_model.py
|
| 4 |
+
|
| 5 |
+
from summon import Summon
|
| 6 |
+
|
| 7 |
+
# ββ Option A: Full pipeline βββββββββββββββββββββββββββββββββββ
|
| 8 |
+
model = (
|
| 9 |
+
Summon.begin("AhmadMeta-v1") # name it whatever you want
|
| 10 |
+
.base("nemotron-mini-4b") # pick your base model
|
| 11 |
+
.corpus(layers=[ # point at your training data
|
| 12 |
+
"data/the_book.jsonl",
|
| 13 |
+
"data/enoch.jsonl",
|
| 14 |
+
"data/circle7.jsonl",
|
| 15 |
+
])
|
| 16 |
+
.constitutional([ # your model's principles
|
| 17 |
+
"truth",
|
| 18 |
+
"sovereignty",
|
| 19 |
+
"evidence",
|
| 20 |
+
"no_deception",
|
| 21 |
+
])
|
| 22 |
+
.license("sovereign-source-v1") # how you release it
|
| 23 |
+
.train(device="cuda", epochs=3) # train on your GPU
|
| 24 |
+
.push("my-org/AhmadMeta-v1") # push to HuggingFace
|
| 25 |
+
)
|
| 26 |
+
|
| 27 |
+
model.manifest()
|
| 28 |
+
|
| 29 |
+
|
| 30 |
+
# ββ Option B: Corpus builder + model separately βββββββββββββββ
|
| 31 |
+
from summon import Summon
|
| 32 |
+
|
| 33 |
+
corpus = (
|
| 34 |
+
Summon.corpus()
|
| 35 |
+
.layer(0, "data/the_book.jsonl", name="genesis")
|
| 36 |
+
.layer(1, "data/enoch.jsonl", name="enochian")
|
| 37 |
+
.layer(2, "data/gospels.jsonl", name="hidden_gospels")
|
| 38 |
+
.layer(3, "data/circle7.jsonl", name="sovereign_lineage")
|
| 39 |
+
.layer(4, "data/book_of_dead.jsonl", name="world_wisdom")
|
| 40 |
+
.layer(5, "data/masters.jsonl", name="masters_of_art")
|
| 41 |
+
.seal()
|
| 42 |
+
)
|
| 43 |
+
|
| 44 |
+
model = (
|
| 45 |
+
Summon.begin("JessicaLM-v1")
|
| 46 |
+
.base("llama3-8b")
|
| 47 |
+
.corpus(corpus_obj=corpus)
|
| 48 |
+
.constitutional(["truth", "care", "sovereignty"])
|
| 49 |
+
.license("apache-2.0")
|
| 50 |
+
.train(device="cuda", epochs=5, batch_size=8)
|
| 51 |
+
.push("jessica-org/JessicaLM-v1")
|
| 52 |
+
)
|
| 53 |
+
|
| 54 |
+
|
| 55 |
+
# ββ Option C: Dry run (validate config, no GPU needed) ββββββββ
|
| 56 |
+
model = (
|
| 57 |
+
Summon.begin("TestModel-v1")
|
| 58 |
+
.base("phi3-mini")
|
| 59 |
+
.corpus(layers=["data/sample.jsonl"])
|
| 60 |
+
.constitutional(["truth"])
|
| 61 |
+
.train(dry_run=True) # just validates config
|
| 62 |
+
)
|
| 63 |
+
|
| 64 |
+
model.manifest()
|
setup.py
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from setuptools import setup, find_packages
|
| 2 |
+
|
| 3 |
+
setup(
|
| 4 |
+
name="summon",
|
| 5 |
+
version="1.0.0",
|
| 6 |
+
description="Sovereign model framework β build your own weights, name your own model",
|
| 7 |
+
long_description=open("README.md").read(),
|
| 8 |
+
long_description_content_type="text/markdown",
|
| 9 |
+
author="Ahmad Ali Parr β SnapKitty Collective",
|
| 10 |
+
license="Apache-2.0",
|
| 11 |
+
packages=find_packages(),
|
| 12 |
+
python_requires=">=3.10",
|
| 13 |
+
install_requires=[],
|
| 14 |
+
extras_require={
|
| 15 |
+
"train": [
|
| 16 |
+
"torch>=2.0",
|
| 17 |
+
"transformers>=4.40",
|
| 18 |
+
"peft>=0.10",
|
| 19 |
+
"trl>=0.8",
|
| 20 |
+
"datasets>=2.18",
|
| 21 |
+
"bitsandbytes>=0.43",
|
| 22 |
+
"accelerate>=0.28",
|
| 23 |
+
],
|
| 24 |
+
"hub": ["huggingface_hub>=0.22"],
|
| 25 |
+
},
|
| 26 |
+
keywords=[
|
| 27 |
+
"sovereign-ai", "qlora", "fine-tuning", "llm",
|
| 28 |
+
"snapkitty", "model-training", "weights"
|
| 29 |
+
],
|
| 30 |
+
classifiers=[
|
| 31 |
+
"Programming Language :: Python :: 3",
|
| 32 |
+
"License :: OSI Approved :: Apache Software License",
|
| 33 |
+
"Topic :: Scientific/Engineering :: Artificial Intelligence",
|
| 34 |
+
],
|
| 35 |
+
)
|
summon/__init__.py
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# summon β sovereign model framework
|
| 2 |
+
# Build your own weights. Name your own model.
|
| 3 |
+
# Apache License 2.0 β SnapKitty Collective 2026
|
| 4 |
+
# pip install summon
|
| 5 |
+
|
| 6 |
+
from .corpus.builder import Corpus
|
| 7 |
+
from .identity.model import SovereignModel
|
| 8 |
+
from .train.runner import Trainer
|
| 9 |
+
|
| 10 |
+
|
| 11 |
+
class Summon:
|
| 12 |
+
"""
|
| 13 |
+
Functional, composable sovereign model builder.
|
| 14 |
+
|
| 15 |
+
Philosophy: Clojure threading (->>), Python syntax.
|
| 16 |
+
Every step is immutable. Every artifact is WORM sealed.
|
| 17 |
+
|
| 18 |
+
Example
|
| 19 |
+
-------
|
| 20 |
+
from summon import Summon
|
| 21 |
+
|
| 22 |
+
model = (
|
| 23 |
+
Summon.begin("MyModel-v1")
|
| 24 |
+
.base("nemotron-mini-4b")
|
| 25 |
+
.corpus(layers=["the_book.jsonl", "enoch.jsonl"])
|
| 26 |
+
.constitutional(["truth", "sovereignty", "evidence"])
|
| 27 |
+
.license("sovereign-source-v1")
|
| 28 |
+
.train()
|
| 29 |
+
.push("my-org/MyModel-v1")
|
| 30 |
+
)
|
| 31 |
+
"""
|
| 32 |
+
|
| 33 |
+
@staticmethod
|
| 34 |
+
def begin(name: str) -> "SovereignModel":
|
| 35 |
+
"""Start building your sovereign model. Give it a name β any name."""
|
| 36 |
+
return SovereignModel(name=name)
|
| 37 |
+
|
| 38 |
+
@staticmethod
|
| 39 |
+
def corpus() -> "Corpus":
|
| 40 |
+
"""Start building a training corpus independently."""
|
| 41 |
+
return Corpus()
|
summon/corpus/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
from .builder import Corpus
|
summon/corpus/builder.py
ADDED
|
@@ -0,0 +1,122 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# summon.corpus β layered corpus builder
|
| 2 |
+
# Apache License 2.0 β SnapKitty Collective 2026
|
| 3 |
+
|
| 4 |
+
import json
|
| 5 |
+
import hashlib
|
| 6 |
+
import time
|
| 7 |
+
from pathlib import Path
|
| 8 |
+
from typing import Optional
|
| 9 |
+
|
| 10 |
+
|
| 11 |
+
class Corpus:
|
| 12 |
+
"""
|
| 13 |
+
Immutable, WORM-sealed corpus builder.
|
| 14 |
+
Add layers. Each layer is a named slice of training data.
|
| 15 |
+
Returns a new Corpus at each step (functional / Clojure style).
|
| 16 |
+
|
| 17 |
+
Example
|
| 18 |
+
-------
|
| 19 |
+
corpus = (
|
| 20 |
+
Corpus()
|
| 21 |
+
.layer(0, "the_book.jsonl", name="genesis")
|
| 22 |
+
.layer(1, "enoch.jsonl", name="enochian")
|
| 23 |
+
.layer(2, "gospels.jsonl", name="hidden_gospels")
|
| 24 |
+
.layer(3, "circle7.jsonl", name="sovereign_lineage")
|
| 25 |
+
.seal()
|
| 26 |
+
)
|
| 27 |
+
"""
|
| 28 |
+
|
| 29 |
+
def __init__(self, layers=None, worm_chain=None):
|
| 30 |
+
self._layers = layers or []
|
| 31 |
+
self._worm_chain = worm_chain or ["GENESIS"]
|
| 32 |
+
self._sealed = False
|
| 33 |
+
|
| 34 |
+
def layer(self, index: int, path: str, name: Optional[str] = None) -> "Corpus":
|
| 35 |
+
"""Add a corpus layer. Returns new Corpus (immutable)."""
|
| 36 |
+
p = Path(path)
|
| 37 |
+
layer_entry = {
|
| 38 |
+
"index": index,
|
| 39 |
+
"name": name or p.stem,
|
| 40 |
+
"path": str(p.resolve()),
|
| 41 |
+
"exists": p.exists(),
|
| 42 |
+
"size_bytes": p.stat().st_size if p.exists() else 0,
|
| 43 |
+
"added_at": time.time(),
|
| 44 |
+
}
|
| 45 |
+
new_chain = self._extend_worm(f"LAYER|{index}|{name or p.stem}")
|
| 46 |
+
new_layers = self._layers + [layer_entry]
|
| 47 |
+
c = Corpus(layers=new_layers, worm_chain=new_chain)
|
| 48 |
+
print(f" [corpus] layer {index} β {name or p.stem} {'β' if p.exists() else 'β path not found yet'}")
|
| 49 |
+
return c
|
| 50 |
+
|
| 51 |
+
def layer_raw(self, index: int, texts: list, name: str) -> "Corpus":
|
| 52 |
+
"""Add a corpus layer directly from a list of strings (no file needed)."""
|
| 53 |
+
layer_entry = {
|
| 54 |
+
"index": index,
|
| 55 |
+
"name": name,
|
| 56 |
+
"path": None,
|
| 57 |
+
"raw_count": len(texts),
|
| 58 |
+
"raw": texts,
|
| 59 |
+
"added_at": time.time(),
|
| 60 |
+
}
|
| 61 |
+
new_chain = self._extend_worm(f"LAYER_RAW|{index}|{name}|{len(texts)}")
|
| 62 |
+
new_layers = self._layers + [layer_entry]
|
| 63 |
+
c = Corpus(layers=new_layers, worm_chain=new_chain)
|
| 64 |
+
print(f" [corpus] layer {index} β {name} ({len(texts)} entries, raw)")
|
| 65 |
+
return c
|
| 66 |
+
|
| 67 |
+
def seal(self) -> "Corpus":
|
| 68 |
+
"""WORM seal the corpus. Produces final hash. Cannot add layers after this."""
|
| 69 |
+
final_hash = self._worm_chain[-1]
|
| 70 |
+
c = Corpus(layers=self._layers, worm_chain=self._worm_chain)
|
| 71 |
+
c._sealed = True
|
| 72 |
+
print(f"\n [corpus] WORM SEALED β {len(self._layers)} layers")
|
| 73 |
+
print(f" [corpus] seal: {final_hash[:32]}...")
|
| 74 |
+
return c
|
| 75 |
+
|
| 76 |
+
def export(self, output_path: str = "corpus_export.jsonl") -> str:
|
| 77 |
+
"""Export all layers to a single JSONL file for training."""
|
| 78 |
+
out = Path(output_path)
|
| 79 |
+
count = 0
|
| 80 |
+
with open(out, "w", encoding="utf-8") as f:
|
| 81 |
+
for layer in self._layers:
|
| 82 |
+
if layer.get("raw"):
|
| 83 |
+
for text in layer["raw"]:
|
| 84 |
+
f.write(json.dumps({
|
| 85 |
+
"text": text,
|
| 86 |
+
"layer": layer["index"],
|
| 87 |
+
"source": layer["name"],
|
| 88 |
+
}) + "\n")
|
| 89 |
+
count += 1
|
| 90 |
+
elif layer.get("path") and Path(layer["path"]).exists():
|
| 91 |
+
with open(layer["path"], encoding="utf-8") as lf:
|
| 92 |
+
for line in lf:
|
| 93 |
+
line = line.strip()
|
| 94 |
+
if line:
|
| 95 |
+
try:
|
| 96 |
+
entry = json.loads(line)
|
| 97 |
+
entry["layer"] = layer["index"]
|
| 98 |
+
entry["source"] = layer["name"]
|
| 99 |
+
f.write(json.dumps(entry) + "\n")
|
| 100 |
+
except json.JSONDecodeError:
|
| 101 |
+
f.write(json.dumps({
|
| 102 |
+
"text": line,
|
| 103 |
+
"layer": layer["index"],
|
| 104 |
+
"source": layer["name"],
|
| 105 |
+
}) + "\n")
|
| 106 |
+
count += 1
|
| 107 |
+
print(f" [corpus] exported {count} entries β {out}")
|
| 108 |
+
return str(out)
|
| 109 |
+
|
| 110 |
+
def summary(self) -> dict:
|
| 111 |
+
return {
|
| 112 |
+
"layers": len(self._layers),
|
| 113 |
+
"sealed": self._sealed,
|
| 114 |
+
"worm_head": self._worm_chain[-1],
|
| 115 |
+
"layer_names": [l["name"] for l in self._layers],
|
| 116 |
+
}
|
| 117 |
+
|
| 118 |
+
def _extend_worm(self, event: str) -> list:
|
| 119 |
+
prev = self._worm_chain[-1]
|
| 120 |
+
msg = f"{prev}|{event}|{time.time()}"
|
| 121 |
+
new_hash = hashlib.sha256(msg.encode()).hexdigest()
|
| 122 |
+
return self._worm_chain + [new_hash]
|
summon/identity/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
from .model import SovereignModel
|
summon/identity/model.py
ADDED
|
@@ -0,0 +1,187 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# summon.identity β sovereign model identity layer
|
| 2 |
+
# You name it. You own it. You release it.
|
| 3 |
+
# Apache License 2.0 β SnapKitty Collective 2026
|
| 4 |
+
|
| 5 |
+
import json
|
| 6 |
+
import hashlib
|
| 7 |
+
import time
|
| 8 |
+
from typing import Optional
|
| 9 |
+
from pathlib import Path
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
SUPPORTED_BASES = {
|
| 13 |
+
"nemotron-mini-4b": "nvidia/Minitron-4B-Base",
|
| 14 |
+
"llama3-8b": "meta-llama/Meta-Llama-3-8B",
|
| 15 |
+
"llama3-70b": "meta-llama/Meta-Llama-3-70B",
|
| 16 |
+
"mistral-7b": "mistralai/Mistral-7B-v0.3",
|
| 17 |
+
"phi3-mini": "microsoft/Phi-3-mini-4k-instruct",
|
| 18 |
+
"qwen2-7b": "Qwen/Qwen2-7B",
|
| 19 |
+
"gemma2-9b": "google/gemma-2-9b",
|
| 20 |
+
"falcon-7b": "tiiuae/falcon-7b",
|
| 21 |
+
}
|
| 22 |
+
|
| 23 |
+
|
| 24 |
+
class SovereignModel:
|
| 25 |
+
"""
|
| 26 |
+
Sovereign model identity + build pipeline.
|
| 27 |
+
Functional / immutable β every method returns a new instance.
|
| 28 |
+
|
| 29 |
+
Example
|
| 30 |
+
-------
|
| 31 |
+
from summon import Summon
|
| 32 |
+
|
| 33 |
+
model = (
|
| 34 |
+
Summon.begin("AhmadMeta-v1")
|
| 35 |
+
.base("nemotron-mini-4b")
|
| 36 |
+
.corpus(layers=["the_book.jsonl", "enoch.jsonl"])
|
| 37 |
+
.constitutional(["truth", "sovereignty", "evidence"])
|
| 38 |
+
.license("sovereign-source-v1")
|
| 39 |
+
.train(device="cuda", epochs=3)
|
| 40 |
+
.push("my-org/AhmadMeta-v1")
|
| 41 |
+
)
|
| 42 |
+
"""
|
| 43 |
+
|
| 44 |
+
def __init__(self, name: str, _state: dict = None):
|
| 45 |
+
self.name = name
|
| 46 |
+
self._state = _state or {
|
| 47 |
+
"name": name,
|
| 48 |
+
"base_id": None,
|
| 49 |
+
"base_hf": None,
|
| 50 |
+
"corpus_path": None,
|
| 51 |
+
"corpus_layers": [],
|
| 52 |
+
"constitution": [],
|
| 53 |
+
"license": "sovereign-source-v1",
|
| 54 |
+
"trained": False,
|
| 55 |
+
"output_path": None,
|
| 56 |
+
"worm_chain": ["GENESIS"],
|
| 57 |
+
"created_at": time.time(),
|
| 58 |
+
}
|
| 59 |
+
print(f"\n ⬑ SUMMON β sovereign model builder")
|
| 60 |
+
print(f" model name: {name}")
|
| 61 |
+
|
| 62 |
+
def base(self, model_id: str) -> "SovereignModel":
|
| 63 |
+
"""
|
| 64 |
+
Set the base model to fine-tune from.
|
| 65 |
+
Use a short name (e.g. 'nemotron-mini-4b') or a full HuggingFace ID.
|
| 66 |
+
"""
|
| 67 |
+
hf_id = SUPPORTED_BASES.get(model_id, model_id)
|
| 68 |
+
state = {**self._state, "base_id": model_id, "base_hf": hf_id}
|
| 69 |
+
state["worm_chain"] = self._worm(state, f"BASE|{model_id}")
|
| 70 |
+
print(f" base: {model_id} β {hf_id}")
|
| 71 |
+
return SovereignModel(self.name, state)
|
| 72 |
+
|
| 73 |
+
def corpus(self, layers: list = None, corpus_obj=None) -> "SovereignModel":
|
| 74 |
+
"""
|
| 75 |
+
Add training corpus. Pass either:
|
| 76 |
+
- layers=[list of .jsonl file paths]
|
| 77 |
+
- corpus_obj=Corpus() instance from summon.Corpus
|
| 78 |
+
"""
|
| 79 |
+
from summon.corpus.builder import Corpus as CorpusBuilder
|
| 80 |
+
if corpus_obj:
|
| 81 |
+
exported = corpus_obj.export(f"{self.name}_corpus.jsonl")
|
| 82 |
+
state = {**self._state, "corpus_path": exported,
|
| 83 |
+
"corpus_layers": corpus_obj.summary()["layer_names"]}
|
| 84 |
+
else:
|
| 85 |
+
layers = layers or []
|
| 86 |
+
state = {**self._state, "corpus_path": None, "corpus_layers": layers}
|
| 87 |
+
print(f" corpus: {len(layers)} layer files")
|
| 88 |
+
state["worm_chain"] = self._worm(state, f"CORPUS|{len(state['corpus_layers'])}")
|
| 89 |
+
return SovereignModel(self.name, state)
|
| 90 |
+
|
| 91 |
+
def constitutional(self, principles: list) -> "SovereignModel":
|
| 92 |
+
"""
|
| 93 |
+
Define the constitutional principles baked into the model.
|
| 94 |
+
These shape RLHF / preference data generation.
|
| 95 |
+
Example: ['truth', 'sovereignty', 'evidence', 'no_deception']
|
| 96 |
+
"""
|
| 97 |
+
state = {**self._state, "constitution": principles}
|
| 98 |
+
state["worm_chain"] = self._worm(state, f"CONSTITUTION|{','.join(principles)}")
|
| 99 |
+
print(f" constitution: {principles}")
|
| 100 |
+
return SovereignModel(self.name, state)
|
| 101 |
+
|
| 102 |
+
def license(self, license_id: str = "sovereign-source-v1") -> "SovereignModel":
|
| 103 |
+
"""
|
| 104 |
+
Set the license for your released weights.
|
| 105 |
+
Common choices: 'sovereign-source-v1', 'apache-2.0', 'mit', 'cc-by-4.0'
|
| 106 |
+
"""
|
| 107 |
+
state = {**self._state, "license": license_id}
|
| 108 |
+
state["worm_chain"] = self._worm(state, f"LICENSE|{license_id}")
|
| 109 |
+
print(f" license: {license_id}")
|
| 110 |
+
return SovereignModel(self.name, state)
|
| 111 |
+
|
| 112 |
+
def train(self, device: str = "cuda", epochs: int = 3,
|
| 113 |
+
batch_size: int = 4, learning_rate: float = 2e-4,
|
| 114 |
+
dry_run: bool = False) -> "SovereignModel":
|
| 115 |
+
"""
|
| 116 |
+
Launch QLoRA fine-tuning on your corpus.
|
| 117 |
+
Requires: transformers, peft, trl, bitsandbytes
|
| 118 |
+
"""
|
| 119 |
+
from summon.train.runner import Trainer
|
| 120 |
+
trainer = Trainer(self._state)
|
| 121 |
+
|
| 122 |
+
if dry_run:
|
| 123 |
+
print(f"\n [train] DRY RUN β config validated")
|
| 124 |
+
trainer.validate()
|
| 125 |
+
state = {**self._state, "trained": False, "output_path": f"./{self.name}_dry"}
|
| 126 |
+
else:
|
| 127 |
+
output_path = trainer.run(
|
| 128 |
+
device=device, epochs=epochs,
|
| 129 |
+
batch_size=batch_size, lr=learning_rate
|
| 130 |
+
)
|
| 131 |
+
state = {**self._state, "trained": True, "output_path": output_path}
|
| 132 |
+
|
| 133 |
+
state["worm_chain"] = self._worm(state, f"TRAIN|epochs={epochs}|device={device}")
|
| 134 |
+
return SovereignModel(self.name, state)
|
| 135 |
+
|
| 136 |
+
def push(self, hub_repo: str, private: bool = False) -> "SovereignModel":
|
| 137 |
+
"""
|
| 138 |
+
Push your trained weights to HuggingFace Hub.
|
| 139 |
+
hub_repo format: 'your-org/YourModelName'
|
| 140 |
+
"""
|
| 141 |
+
if not self._state.get("trained"):
|
| 142 |
+
print(f" [push] β model not trained yet β run .train() first")
|
| 143 |
+
return self
|
| 144 |
+
|
| 145 |
+
try:
|
| 146 |
+
from huggingface_hub import HfApi
|
| 147 |
+
api = HfApi()
|
| 148 |
+
output = self._state.get("output_path", f"./{self.name}")
|
| 149 |
+
api.upload_folder(
|
| 150 |
+
folder_path=output,
|
| 151 |
+
repo_id=hub_repo,
|
| 152 |
+
repo_type="model",
|
| 153 |
+
private=private,
|
| 154 |
+
)
|
| 155 |
+
print(f" [push] β {self.name} β huggingface.co/{hub_repo}")
|
| 156 |
+
except ImportError:
|
| 157 |
+
print(f" [push] install huggingface_hub: pip install huggingface_hub")
|
| 158 |
+
except Exception as e:
|
| 159 |
+
print(f" [push] error: {e}")
|
| 160 |
+
|
| 161 |
+
state = {**self._state}
|
| 162 |
+
state["worm_chain"] = self._worm(state, f"PUSH|{hub_repo}")
|
| 163 |
+
return SovereignModel(self.name, state)
|
| 164 |
+
|
| 165 |
+
def manifest(self, output: str = None) -> dict:
|
| 166 |
+
"""Print and return the full model manifest β WORM sealed."""
|
| 167 |
+
m = {
|
| 168 |
+
"name": self.name,
|
| 169 |
+
"base": self._state["base_hf"],
|
| 170 |
+
"corpus": self._state["corpus_layers"],
|
| 171 |
+
"constitution": self._state["constitution"],
|
| 172 |
+
"license": self._state["license"],
|
| 173 |
+
"trained": self._state["trained"],
|
| 174 |
+
"output_path": self._state["output_path"],
|
| 175 |
+
"worm_head": self._state["worm_chain"][-1],
|
| 176 |
+
"created_at": self._state["created_at"],
|
| 177 |
+
}
|
| 178 |
+
print(json.dumps(m, indent=2))
|
| 179 |
+
if output:
|
| 180 |
+
Path(output).write_text(json.dumps(m, indent=2))
|
| 181 |
+
return m
|
| 182 |
+
|
| 183 |
+
def _worm(self, state: dict, event: str) -> list:
|
| 184 |
+
prev = state["worm_chain"][-1]
|
| 185 |
+
msg = f"{prev}|{event}|{time.time()}"
|
| 186 |
+
h = hashlib.sha256(msg.encode()).hexdigest()
|
| 187 |
+
return state["worm_chain"] + [h]
|
summon/train/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
from .runner import Trainer
|
summon/train/runner.py
ADDED
|
@@ -0,0 +1,151 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# summon.train β QLoRA fine-tuning runner
|
| 2 |
+
# Apache License 2.0 β SnapKitty Collective 2026
|
| 3 |
+
|
| 4 |
+
import json
|
| 5 |
+
import time
|
| 6 |
+
from pathlib import Path
|
| 7 |
+
|
| 8 |
+
|
| 9 |
+
class Trainer:
|
| 10 |
+
"""QLoRA fine-tuning runner. Wraps HuggingFace PEFT + TRL."""
|
| 11 |
+
|
| 12 |
+
def __init__(self, state: dict):
|
| 13 |
+
self._state = state
|
| 14 |
+
|
| 15 |
+
def validate(self):
|
| 16 |
+
issues = []
|
| 17 |
+
if not self._state.get("base_hf"):
|
| 18 |
+
issues.append("no base model set β call .base()")
|
| 19 |
+
if not self._state.get("corpus_layers") and not self._state.get("corpus_path"):
|
| 20 |
+
issues.append("no corpus set β call .corpus()")
|
| 21 |
+
if issues:
|
| 22 |
+
for i in issues:
|
| 23 |
+
print(f" [train] β {i}")
|
| 24 |
+
return False
|
| 25 |
+
print(f" [train] β config valid")
|
| 26 |
+
print(f" [train] base: {self._state['base_hf']}")
|
| 27 |
+
print(f" [train] corpus: {self._state.get('corpus_path') or self._state.get('corpus_layers')}")
|
| 28 |
+
print(f" [train] license: {self._state.get('license')}")
|
| 29 |
+
return True
|
| 30 |
+
|
| 31 |
+
def run(self, device: str = "cuda", epochs: int = 3,
|
| 32 |
+
batch_size: int = 4, lr: float = 2e-4) -> str:
|
| 33 |
+
if not self.validate():
|
| 34 |
+
raise ValueError("Model config invalid β fix issues above before training")
|
| 35 |
+
|
| 36 |
+
output_path = f"./{self._state['name']}_weights"
|
| 37 |
+
Path(output_path).mkdir(exist_ok=True)
|
| 38 |
+
|
| 39 |
+
try:
|
| 40 |
+
import torch
|
| 41 |
+
from transformers import AutoTokenizer, AutoModelForCausalLM, TrainingArguments
|
| 42 |
+
from peft import LoraConfig, get_peft_model, TaskType
|
| 43 |
+
from trl import SFTTrainer
|
| 44 |
+
from datasets import load_dataset
|
| 45 |
+
except ImportError as e:
|
| 46 |
+
print(f"\n [train] Missing dependency: {e}")
|
| 47 |
+
print(f" [train] Install: pip install transformers peft trl datasets bitsandbytes accelerate")
|
| 48 |
+
raise
|
| 49 |
+
|
| 50 |
+
print(f"\n [train] Loading base model: {self._state['base_hf']}")
|
| 51 |
+
tokenizer = AutoTokenizer.from_pretrained(self._state["base_hf"])
|
| 52 |
+
tokenizer.pad_token = tokenizer.eos_token
|
| 53 |
+
|
| 54 |
+
import torch
|
| 55 |
+
from transformers import BitsAndBytesConfig
|
| 56 |
+
bnb_config = BitsAndBytesConfig(
|
| 57 |
+
load_in_4bit=True,
|
| 58 |
+
bnb_4bit_quant_type="nf4",
|
| 59 |
+
bnb_4bit_compute_dtype=torch.float16,
|
| 60 |
+
bnb_4bit_use_double_quant=True,
|
| 61 |
+
)
|
| 62 |
+
model = AutoModelForCausalLM.from_pretrained(
|
| 63 |
+
self._state["base_hf"],
|
| 64 |
+
quantization_config=bnb_config,
|
| 65 |
+
device_map="auto",
|
| 66 |
+
)
|
| 67 |
+
model.config.use_cache = False
|
| 68 |
+
|
| 69 |
+
lora_config = LoraConfig(
|
| 70 |
+
r=16, lora_alpha=32,
|
| 71 |
+
target_modules=["q_proj", "v_proj", "k_proj", "o_proj"],
|
| 72 |
+
lora_dropout=0.05,
|
| 73 |
+
bias="none",
|
| 74 |
+
task_type=TaskType.CAUSAL_LM,
|
| 75 |
+
)
|
| 76 |
+
model = get_peft_model(model, lora_config)
|
| 77 |
+
model.print_trainable_parameters()
|
| 78 |
+
|
| 79 |
+
corpus_path = self._state.get("corpus_path")
|
| 80 |
+
if not corpus_path:
|
| 81 |
+
raise ValueError("Export corpus first: corpus.export()")
|
| 82 |
+
|
| 83 |
+
dataset = load_dataset("json", data_files=corpus_path, split="train")
|
| 84 |
+
|
| 85 |
+
training_args = TrainingArguments(
|
| 86 |
+
output_dir=output_path,
|
| 87 |
+
num_train_epochs=epochs,
|
| 88 |
+
per_device_train_batch_size=batch_size,
|
| 89 |
+
gradient_accumulation_steps=4,
|
| 90 |
+
learning_rate=lr,
|
| 91 |
+
fp16=True,
|
| 92 |
+
logging_steps=10,
|
| 93 |
+
save_strategy="epoch",
|
| 94 |
+
report_to="none",
|
| 95 |
+
)
|
| 96 |
+
|
| 97 |
+
trainer = SFTTrainer(
|
| 98 |
+
model=model,
|
| 99 |
+
train_dataset=dataset,
|
| 100 |
+
args=training_args,
|
| 101 |
+
tokenizer=tokenizer,
|
| 102 |
+
dataset_text_field="text",
|
| 103 |
+
max_seq_length=2048,
|
| 104 |
+
)
|
| 105 |
+
|
| 106 |
+
print(f" [train] Starting QLoRA β {epochs} epochs on {device}")
|
| 107 |
+
trainer.train()
|
| 108 |
+
trainer.save_model(output_path)
|
| 109 |
+
tokenizer.save_pretrained(output_path)
|
| 110 |
+
|
| 111 |
+
# Write model card
|
| 112 |
+
self._write_model_card(output_path)
|
| 113 |
+
print(f"\n [train] β Complete β weights at {output_path}")
|
| 114 |
+
return output_path
|
| 115 |
+
|
| 116 |
+
def _write_model_card(self, output_path: str):
|
| 117 |
+
state = self._state
|
| 118 |
+
card = f"""---
|
| 119 |
+
license: {state.get('license', 'sovereign-source-v1')}
|
| 120 |
+
base_model: {state.get('base_hf')}
|
| 121 |
+
tags:
|
| 122 |
+
- sovereign-ai
|
| 123 |
+
- snapkitty
|
| 124 |
+
- qlora
|
| 125 |
+
- summon
|
| 126 |
+
---
|
| 127 |
+
|
| 128 |
+
# {state['name']}
|
| 129 |
+
|
| 130 |
+
Built with [Summon](https://github.com/SNAPKITTYWEST/summon) β SnapKitty sovereign model framework.
|
| 131 |
+
|
| 132 |
+
## Model Details
|
| 133 |
+
|
| 134 |
+
- **Base model**: {state.get('base_hf')}
|
| 135 |
+
- **Training**: QLoRA (4-bit quantized fine-tuning)
|
| 136 |
+
- **License**: {state.get('license')}
|
| 137 |
+
- **Constitution**: {state.get('constitution', [])}
|
| 138 |
+
- **Built by**: {state['name']} β powered by Summon
|
| 139 |
+
|
| 140 |
+
## Corpus Layers
|
| 141 |
+
|
| 142 |
+
{chr(10).join(f'- {l}' for l in state.get('corpus_layers', []))}
|
| 143 |
+
|
| 144 |
+
## WORM Seal
|
| 145 |
+
|
| 146 |
+
`{state['worm_chain'][-1]}`
|
| 147 |
+
|
| 148 |
+
---
|
| 149 |
+
*Built with Summon β SnapKitty Collective β Evidence or Silence*
|
| 150 |
+
"""
|
| 151 |
+
Path(output_path, "README.md").write_text(card)
|