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