Instructions to use schneewolflabs/Compactor-Qwen3.5-4B with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- PEFT
How to use schneewolflabs/Compactor-Qwen3.5-4B with PEFT:
from peft import PeftModel from transformers import AutoModelForCausalLM base_model = AutoModelForCausalLM.from_pretrained("Lazarus-Ai/ReAligned-Qwen3.5-4B") model = PeftModel.from_pretrained(base_model, "schneewolflabs/Compactor-Qwen3.5-4B") - Notebooks
- Google Colab
- Kaggle
File size: 5,407 Bytes
00bb63e | 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 | """Does the summary preserve what is needed to continue?
Loss is the wrong headline. The failure this tune exists to fix is specific: a summary that drops
the task, after which the agent invents a new one — sixteen searches of research compacted into 396
characters, answered with "fresh project scaffolded". So the metric is preservation, not fluency.
Four checks, all deterministic, no judge:
- **has_task** — is there a `Task:` line at all. The reference summaries all have one.
- **task_overlap** — word-level F1 between the generated task line and the reference's. Catches a
summary that states *a* task, just not this one.
- **fact_recall** — fraction of the reference's distinctive tokens (paths, identifiers, numbers,
errors) that survive into the generated summary. This is the one that catches a fluent summary
with the specifics sanded off, which is what a small model tends to produce.
- **compression** — length ratio. A "summary" that is 90% of the input has not compacted anything,
and one that is 2% has thrown the state away.
Run base and tuned through the identical path; the difference is the adapter.
Usage:
python3 eval_compaction.py --out base_eval.json
python3 eval_compaction.py --adapter out/adapter --out tuned_eval.json
python3 eval_compaction.py --compare base_eval.json tuned_eval.json
"""
import argparse
import json
import re
import sys
MODEL = "Lazarus-Ai/ReAligned-Qwen3.5-4B"
# Tokens worth preserving: paths, dotted identifiers, CamelCase, numbers with units, errors.
DISTINCTIVE = re.compile(
r"(?:/[\w.\-/]{4,})"
r"|(?:\b\w+\.(?:ts|py|json|jsonl|toml|md|sh|jinja|gguf|safetensors)\b)"
r"|(?:\b[A-Z][a-z]+[A-Z]\w+\b)"
r"|(?:\b\d{2,}(?:\.\d+)?[kKmMgG]?\b)"
r"|(?:\b[A-Z][A-Z_]{3,}\b)"
)
def task_line(text: str) -> str:
m = re.search(r"^\s*task:\s*(.+)$", text, re.I | re.M)
return m.group(1).strip() if m else ""
def f1(a: str, b: str) -> float:
ta = {w.lower() for w in re.findall(r"\w+", a) if len(w) > 2}
tb = {w.lower() for w in re.findall(r"\w+", b) if len(w) > 2}
if not ta or not tb:
return 0.0
hit = len(ta & tb)
p, r = hit / len(ta), hit / len(tb)
return 0.0 if p + r == 0 else 2 * p * r / (p + r)
def score(generated: str, reference: str, prompt: str) -> dict:
ref_facts = set(DISTINCTIVE.findall(reference))
gen_facts = set(DISTINCTIVE.findall(generated))
return {
"has_task": bool(task_line(generated)),
"task_overlap": round(f1(task_line(generated), task_line(reference)), 3),
"fact_recall": round(len(ref_facts & gen_facts) / len(ref_facts), 3) if ref_facts else None,
"compression": round(len(generated) / max(len(prompt), 1), 4),
"chars": len(generated),
}
def run(args) -> None:
import torch
from transformers import AutoModelForImageTextToText, AutoTokenizer
tok = AutoTokenizer.from_pretrained(MODEL)
tok.padding_side = "left"
if tok.pad_token_id is None:
tok.pad_token = tok.eos_token
model = AutoModelForImageTextToText.from_pretrained(
MODEL, dtype=torch.bfloat16, device_map="cuda:0")
if args.adapter:
from peft import PeftModel
model = PeftModel.from_pretrained(model, args.adapter)
model.eval()
rows = [json.loads(l) for l in open(args.val)][: args.limit]
results = []
for i, r in enumerate(rows):
enc = tok(r["prompt"], return_tensors="pt").to(model.device)
with torch.no_grad():
out = model.generate(**enc, max_new_tokens=1200, do_sample=False,
pad_token_id=tok.pad_token_id)
gen = tok.decode(out[0][enc["input_ids"].shape[1]:], skip_special_tokens=True).strip()
s = score(gen, r["chosen"], r["prompt"])
s["text"] = gen[:1500]
results.append(s)
print(f"[{i+1}/{len(rows)}] task={s['has_task']} overlap={s['task_overlap']} "
f"facts={s['fact_recall']} chars={s['chars']}", flush=True)
summarise(results, args.adapter or "base")
json.dump({"adapter": args.adapter, "results": results}, open(args.out, "w"), indent=1)
def summarise(results: list, label: str) -> None:
n = len(results)
has = sum(r["has_task"] for r in results)
ov = [r["task_overlap"] for r in results]
fr = [r["fact_recall"] for r in results if r["fact_recall"] is not None]
comp = [r["compression"] for r in results]
print(f"\n--- {label} ({n} held-out windows) ---")
print(f" has Task: line {has}/{n} ({100*has/n:.0f}%)")
print(f" task overlap {sum(ov)/n:.3f} mean")
print(f" fact recall {sum(fr)/len(fr):.3f} mean" if fr else " fact recall n/a")
print(f" compression {sum(comp)/n:.3f} mean (reference ~0.12)")
print(f" mean chars {sum(r['chars'] for r in results)//n}")
def compare(a: str, b: str) -> int:
for path in (a, b):
d = json.load(open(path))
summarise(d["results"], d.get("adapter") or "base")
return 0
if __name__ == "__main__":
ap = argparse.ArgumentParser()
ap.add_argument("--adapter")
ap.add_argument("--val", default="compaction_val.jsonl")
ap.add_argument("--limit", type=int, default=45)
ap.add_argument("--out", default="compaction_eval.json")
ap.add_argument("--compare", nargs=2)
a = ap.parse_args()
sys.exit(compare(*a.compare) if a.compare else run(a))
|