Instructions to use kongyo/Ketsudan-27B with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Local Apps Settings
- Unsloth Desktop
File size: 13,067 Bytes
a86df9f | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 | """Write the model card and publish the trained decision model to the Hugging Face Hub.
python finalize.py --repo kongyo/Ketsudan-27B --name final2 [--public]
Reads the run's reports from /content/out, writes README.md next to the adapters and head,
uploads the folder to the repo root, the evaluation files to eval/ and this code to code/.
"""
import argparse
import json
import os
parser = argparse.ArgumentParser()
parser.add_argument("--repo", required = True)
parser.add_argument("--out", default = "/content/out")
parser.add_argument("--name", default = "final")
parser.add_argument("--public", action = "store_true")
parser.add_argument("--dry-run", action = "store_true")
args = parser.parse_args()
folder = f"{args.out}/{args.name}"
report = json.load(open(f"{folder}/train_report.json", encoding = "utf-8"))
model_name = args.repo.split("/")[-1]
def load(path):
path = f"{args.out}/{path}"
return json.load(open(path, encoding = "utf-8")) if os.path.exists(path) else None
def pct(x):
return "–" if x is None else f"{x:.3f}"
ours_td = report["eval"]["typed_decisions_test"]
jev_td = load("jev_td_test_metrics.json")
ours_ho_parts = [load(f"{args.name}_heldout_metrics.json") or report["eval"].get("heldout"), load(f"{args.name}_heldout2_metrics.json")]
jev_ho_parts = [load("jev_heldout_metrics.json"), load("jev_heldout2_metrics.json")]
def merged(parts, key = "accuracy_by_workflow"):
out = {}
for part in parts:
if part:
out.update(part[key])
return out
ours_ho, jev_ho = merged(ours_ho_parts), merged(jev_ho_parts)
ours_ece = [p["ece"] for p in ours_ho_parts if p]
jev_ece = [p["ece"] for p in jev_ho_parts if p]
def bold_pair(a, b, lower_better = False):
better_a = (a <= b) if lower_better else (a >= b)
return (f"**{pct(a)}**" if better_a else pct(a)), (pct(b) if better_a else f"**{pct(b)}**")
def td_row(name, m):
t = m["accuracy_by_type"]
return (f"| {name} | {pct(m['accuracy'])} | {pct(t.get('noul'))} | {pct(t.get('choice'))} | {pct(t.get('score'))} | "
f"{pct(m['kl_from_gold'])} | {pct(m['brier'])} | {pct(m['ece'])} | {pct(m['record_accuracy'])} |")
td_lines = [
"| Model | Accuracy ↑ | noul | choice | score | KL from gold ↓ | Brier ↓ | ECE ↓ | All 5 right ↑ |",
"|---|---:|---:|---:|---:|---:|---:|---:|---:|",
td_row(f"**{model_name}** (this model)", ours_td),
]
if jev_td:
td_lines.append(td_row("TypeSafe Jev (`jev-latest` → jev-1.13.0, same day)", jev_td))
td_table = "\n".join(td_lines)
claim = ""
if jev_td:
cols = [ours_td["accuracy"] >= jev_td["accuracy"], ours_td["kl_from_gold"] <= jev_td["kl_from_gold"],
ours_td["brier"] <= jev_td["brier"], ours_td["ece"] <= jev_td["ece"],
ours_td["record_accuracy"] >= jev_td["record_accuracy"]]
types = [ours_td["accuracy_by_type"][k] >= v for k, v in jev_td["accuracy_by_type"].items()]
flows = [ours_td["accuracy_by_workflow"][k] >= v for k, v in jev_td["accuracy_by_workflow"].items()]
if all(cols + types + flows):
claim = f"{model_name} matches or beats Jev on every column, every question type and every workflow."
else:
claim = (f"{model_name} is ahead of Jev on {sum(cols)} of 5 summary columns, {sum(types)} of {len(types)} "
f"question types and {sum(flows)} of {len(flows)} workflows.")
LABELS = {
"anli": "ANLI test r1–r3 (400, 3-way NLI)",
"winogrande": "WinoGrande dev (400, commonsense pronoun / blank)",
"hellaswag": "HellaSwag validation (400, 4 endings)",
"emotion": "dair-ai/emotion test (400, 6 emotions)",
}
both = [b for b in LABELS if b in ours_ho and b in jev_ho]
ho_lines = ["| Benchmark (rows) | This model | Jev (same requests, same day) |", "|---|---:|---:|"]
for b in both:
a, j = bold_pair(ours_ho[b], jev_ho[b])
ho_lines.append(f"| {LABELS[b]} | {a} | {j} |")
if both:
a, j = bold_pair(sum(ours_ho[b] for b in both) / len(both), sum(jev_ho[b] for b in both) / len(both))
ho_lines.append(f"| Mean accuracy | {a} | {j} |")
if ours_ece and jev_ece:
a, j = bold_pair(sum(ours_ece) / len(ours_ece), sum(jev_ece) / len(jev_ece), lower_better = True)
ho_lines.append(f"| Mean ECE ↓ | {a} | {j} |")
ho_table = "\n".join(ho_lines)
losses = [LABELS[b].split(" (")[0] for b in both if ours_ho[b] < jev_ho[b]]
weak = []
if losses:
weak.append(f"* Trails Jev on {', '.join(losses)} (see the held-out table).")
if ours_ece and sum(ours_ece) / len(ours_ece) > 0.08:
weak.append("* Confidence outside the training distribution is less reliable (held-out ECE above). Refit the "
"temperatures on a few hundred labelled examples of your own task (`FastDecisionModel.calibrate`).")
weak.append("* English only was evaluated. Text input only (the backbone's vision tower is not trained).")
weak.append("* Numbers, dates and counting belong in code, as for any System One style model.")
weak_text = "\n".join(weak)
stage2 = report.get("stage2")
s1 = report["args"]
training_rows = [
f"| Stage 1 | LoRA r={s1['lora_r']} on every language-model projection (attention, Gated DeltaNet, MLP) + a fresh joint schema head; "
f"lr {s1['lr']} (head {s1['head_lr']}), cosine, soft-target cross-entropy, one pass over {report['train_items'] - (stage2 or {}).get('train_items', 0):,} records "
f"(all 1,120 typed-decisions train cases, n4ze3m/typed-decisions-synth, Unsloth's public mixture) in a 32-minute budget |",
]
if stage2:
sources = ", ".join(f"{k} ({v})" for k, v in stage2["sources"].items())
training_rows.append(
f"| Stage 2 | continued from stage 1 at lr {stage2['args']['lr']} (head {stage2['args']['head_lr']}): {stage2['train_items']:,} records "
f"= {stage2['td_replay']} typed-decisions cases replayed + commonsense / multiple-choice sources {sources} + fresh public-mixture and synthetic rows |"
)
training_rows.append("| Calibration | per-type temperatures refit on a mixed holdout: 80 unseen typed-decisions train cases + 320 unseen general items |")
else:
training_rows.append("| Calibration | per-type temperatures fitted on 80 held-out typed-decisions train cases |")
training_rows.append(f"| Total | {report['train_items']:,} records, {report['train_tokens'] / 1e6:.2f}M tokens, {report['steps']} optimizer steps of 16 |")
minutes = (report.get("train_metrics", {}).get("train_runtime", 0) + (stage2 or {}).get("train_metrics", {}).get("train_runtime", 0)) / 60
training_rows.append(f"| Hardware | 1 × {report.get('gpu')} (Google Colab G4), {minutes:.0f} minutes of optimizer time in total |")
training_table = "\n".join(training_rows)
card = f"""---
license: apache-2.0
base_model: Qwen/Qwen3.8-27B
library_name: unsloth
pipeline_tag: text-classification
tags:
- decision-model
- typed-decisions
- system-one-compatible
- calibration
- clef-head
- unsloth
- lora
datasets:
- LocalLLaMA/typed-decisions
- n4ze3m/typed-decisions-synth
language:
- en
---
# {model_name} — a calibrated decision model on Qwen3.8-27B
**{model_name}** (決断, "decision") turns [Qwen/Qwen3.8-27B](https://huggingface.co/Qwen/Qwen3.8-27B) into a
*decision model*: it reads a `state` and typed questions — `choice`, `noul` (yes/no) and `score` (ordered levels) — and
returns a calibrated probability for every option instead of generating text. Requests and answers use the JSON shape of
a `POST /v1/systemone` body, so Jev-style requests work unchanged.
* **Backbone:** Qwen/Qwen3.8-27B (bf16) with LoRA adapters on every language-model projection.
* **Head:** a joint schema head of Cloudflare's [Clef](https://huggingface.co/Cloudflare/clef) design, trained from scratch
(no Clef weights are used), through Unsloth's `FastDecisionModel`.
* **Open weights, Apache-2.0**, ~1.2 GB of adapters and head on top of the public base model.
Not affiliated with TypeSafe AI, Cloudflare, Unsloth or Qwen.
## Results
### typed-decisions test (400 cases × 5 questions = 2,000 decisions)
{td_table}
Same 400 requests, same scorer, same day ([`code/td_metrics.py`](code/td_metrics.py): accuracy = argmax vs the gold
label; KL(gold‖pred) with 1e-6 clipping; Brier = mean over options of the squared error; ECE = 10 bins of the top
probability). {claim} Per-workflow numbers are in [`eval/`](eval).
**Read this caveat:** {model_name} was trained on the typed-decisions `train` split (fitted mode); Jev never saw these
workflows. For reference, the dataset card lists TypeSafe Jev 1.13.0 at 0.727 (zero-shot) and models fitted on `train` at
0.766–0.801 (OpenDecider-large-td 0.801, od1 0.797, OpenDecider-nano 0.796, OpenDecider-small-td 0.792); those numbers come
from the dataset maintainers' or submitters' scorers, not ours, and the accuracy column is the only one that is comparable.
### Held-out tasks neither model was trained on
{ho_table}
Identical requests (same state, instructions and options) were sent to both models on the same day. These are
general zero-shot decisions outside the training data.
## Known weaknesses
{weak_text}
## Usage
```python
from unsloth import FastDecisionModel
model, tokenizer = FastDecisionModel.from_pretrained("{args.repo}") # Qwen/Qwen3.8-27B + adapters + head
FastDecisionModel.for_inference(model)
answers = FastDecisionModel.predict(
model, tokenizer,
"Hi, I was charged twice for invoice #4411. Please refund the duplicate today.",
{{
"team": {{"type": "choice", "instructions": "Which team should handle this?",
"criteria": {{"billing": "invoices, payments, refunds", "technical": "bugs, outages, errors",
"sales": "pricing, new plans"}}}},
"refund": {{"type": "noul", "instructions": "Does the customer ask for a refund?"}},
"urgency": {{"type": "score", "instructions": "How urgent is this?",
"criteria": ["not urgent", "soon", "today"]}},
}},
)
print(answers["team"]["answer"], answers["team"]["probabilities"])
```
About 56 GB of GPU memory in bf16; `load_in_4bit=True` runs it on a 24 GB GPU. Every answer carries `probabilities` over
all options; `noul` answers carry the probability of yes. Unsloth Studio can serve the folder behind a
TypeSafe-compatible `/v1/systemone` endpoint, so code written for the `typesafe-sdk` can point `TYPESAFE_BASE_URL` at it.
## Training
| | |
|---|---|
{training_table}
**No output of TypeSafe's Jev was used for training, calibration or model selection** (TypeSafe's terms forbid
distillation). Jev was only called to score it on the evaluation sets above.
**Decontamination.** Synthetic, public and commonsense training rows share no 13-word shingle with any evaluation state
(typed-decisions test, ANLI, WinoGrande; in stage 2 also HellaSwag and emotion). typed-decisions train has no state
identical to a test state (its cases share JSON boilerplate with the test cases, so shingles would flag most of them).
HellaSwag and emotion were not shingle-checked in stage 1; no stage-1 source draws on them.
## Licenses
Weights: Apache-2.0 (base model Apache-2.0). Training data keeps its own licenses: typed-decisions (Apache-2.0),
typed-decisions-synth (MIT), BANKING77 (CC-BY-4.0), CLINC150 (CC-BY-3.0), MNLI (mixed), SNLI (CC-BY-SA-4.0), WANLI
(CC-BY-4.0), BoolQ (CC-BY-SA-3.0), MMLU / CommonsenseQA (MIT), ARC (CC-BY-SA-4.0), OpenBookQA (Apache-2.0), COPA / WSC
(SuperGLUE terms), Social IQa and SWAG (as published by AI2), prompt-injections (Apache-2.0); AG News and SST-5 list no
license. The head follows Cloudflare's Clef design (Apache-2.0) as implemented in Unsloth.
"""
with open(f"{folder}/README.md", "w", encoding = "utf-8") as f:
f.write(card)
if args.dry_run:
print(card)
raise SystemExit(0)
from huggingface_hub import HfApi
api = HfApi()
api.create_repo(args.repo, private = True, exist_ok = True)
api.upload_folder(folder_path = folder, repo_id = args.repo, commit_message = f"Decision model ({args.name}): adapters, head, calibration, card")
for name in sorted(os.listdir(args.out)):
# Jev's raw answers stay private (only its aggregate metrics are published), so nobody can distil from them.
if name.endswith((".json", ".jsonl")) and not name.startswith("_") and "rows" not in name and not (name.startswith("jev_") and name.endswith(".jsonl")):
api.upload_file(path_or_fileobj = f"{args.out}/{name}", path_in_repo = f"eval/{name}", repo_id = args.repo)
code = os.path.dirname(os.path.abspath(__file__))
for name in sorted(os.listdir(code)):
if name.endswith((".py", ".sh")):
api.upload_file(path_or_fileobj = f"{code}/{name}", path_in_repo = f"code/{name}", repo_id = args.repo)
files = api.list_repo_files(args.repo)
for stale in ("final",):
if any(path.startswith(f"{stale}/") for path in files):
api.delete_folder(stale, repo_id = args.repo, commit_message = "Remove the training checkpoint copy")
if args.public:
api.update_repo_settings(args.repo, private = False)
print("FINALIZED", args.repo, args.name, "public" if args.public else "private", flush = True)
|