Instructions to use moncefem/memory-lora-gemma4 with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- PEFT
How to use moncefem/memory-lora-gemma4 with PEFT:
Task type is invalid.
- Notebooks
- Google Colab
- Kaggle
| #!/usr/bin/env python3 | |
| """Benchmark a generated adapter against the frozen base model on ONE repo. | |
| Builds the question set from the repo's ACTUAL contents (deps, test framework, | |
| packaging, layout, license, entry points) rather than hand-written guesses, so | |
| the gold answers are ground truth rather than opinion. Then scores base vs | |
| adapted on the same model instance, adapter toggled with `disable_adapter()`. | |
| Three task families, because an adapter can help one and hurt another -- which | |
| is exactly what happened here (QA improved hugely while raw-text modelling | |
| regressed), and a single number would have hidden it: | |
| FACT - "Q: <question>\\nA:" -> short factual answer. The trained format. | |
| CODE - completion of real lines taken from the repo's own source. | |
| TEXT - plain continuation of repo prose (README/docstrings). | |
| Metrics per family: | |
| loss teacher-forced cross-entropy on the gold answer (lower better) | |
| win rate fraction of items where adapted loss < base loss | |
| keyword fraction of greedy generations containing the gold keyword | |
| Usage: | |
| python benchmark_repo.py --job <jobId> --checkpoint ../../runs/h200_run/head.best.pt | |
| """ | |
| from __future__ import annotations | |
| import argparse | |
| import json | |
| import re | |
| import sys | |
| from pathlib import Path | |
| import numpy as np | |
| import torch | |
| HERE = Path(__file__).resolve().parent | |
| sys.path.insert(0, str(HERE.parent / "engine")) | |
| import config # noqa: E402 | |
| from generate_and_merge import (TARGET_MODULES, load_head, # noqa: E402 | |
| _lora_modules_by_spec_name) | |
| from memory_lora.core import DEFAULT_ROOT_PREFIX, get_module_specs # noqa: E402 | |
| SKIP = {".git", "__pycache__", ".venv", "node_modules", "build", "dist", ".tox"} | |
| # -------------------------------------------------------------------------- | |
| # Build the benchmark from repo ground truth | |
| # -------------------------------------------------------------------------- | |
| def _read(p: Path, n: int = 20000) -> str: | |
| try: | |
| return p.read_text(errors="ignore")[:n] | |
| except OSError: | |
| return "" | |
| def build_fact_items(repo: Path) -> list[dict]: | |
| """Derive Q/A pairs whose answers are verifiable from the repo itself.""" | |
| items: list[dict] = [] | |
| pyproject = _read(repo / "pyproject.toml") | |
| setup_py = _read(repo / "setup.py") | |
| setup_cfg = _read(repo / "setup.cfg") | |
| build = pyproject + setup_py + setup_cfg | |
| # packaging backend | |
| if "setuptools" in build: | |
| items.append(dict(q="What packaging tool does this project use?", | |
| a=" setuptools", kw="setuptools")) | |
| elif "poetry" in build.lower(): | |
| items.append(dict(q="What packaging tool does this project use?", | |
| a=" poetry", kw="poetry")) | |
| if "hatchling" in build: | |
| items.append(dict(q="What build backend does this project declare?", | |
| a=" hatchling", kw="hatchling")) | |
| # test framework | |
| test_files = [p for p in repo.rglob("*.py") | |
| if not any(s in p.parts for s in SKIP) | |
| and ("test" in p.name.lower() or "tests" in p.parts)] | |
| joined = " ".join(_read(p, 4000) for p in test_files[:12]) | |
| if "pytest" in joined or "pytest" in build: | |
| items.append(dict(q="What testing framework does this repository use?", | |
| a=" pytest", kw="pytest")) | |
| elif "unittest" in joined: | |
| items.append(dict(q="What testing framework does this repository use?", | |
| a=" unittest", kw="unittest")) | |
| # license | |
| lic = _read(repo / "LICENSE") + _read(repo / "LICENSE.txt") | |
| for name, key in (("Apache", "Apache"), ("MIT", "MIT"), | |
| ("BSD", "BSD"), ("GNU", "GPL")): | |
| if name.lower() in lic.lower()[:400]: | |
| items.append(dict(q="What license does this project use?", | |
| a=f" {key}", kw=key)) | |
| break | |
| # top-level package | |
| EXCL = {"tests", "test", "docs", "doc", "examples", "example", "scripts", | |
| "benchmarks", "ext"} | |
| pkgs = [d.name for d in repo.iterdir() | |
| if d.is_dir() and (d / "__init__.py").exists() | |
| and d.name not in SKIP and d.name.lower() not in EXCL] | |
| if pkgs: | |
| # Prefer the package named after the repo (src/<name> layouts included). | |
| best = next((k for k in pkgs if k.lower() == repo.name.lower()), pkgs[0]) | |
| items.append(dict(q="What is the name of the main Python package in this repository?", | |
| a=f" {best}", kw=best)) | |
| # dependencies | |
| # Parse only INSIDE a dependency list, otherwise setup.py keywords such as | |
| # `install_requires=` / `python_requires=` get matched as package names. | |
| dep_block = "" | |
| for pat in (r"install_requires\s*=\s*\[(.*?)\]", | |
| r"dependencies\s*=\s*\[(.*?)\]", | |
| r"\[project\.dependencies\](.*?)(?:\n\[|\Z)"): | |
| m = re.search(pat, build, re.S) | |
| if m: | |
| dep_block = m.group(1) | |
| break | |
| NOT_PKG = {"python", "name", "version", "requires", "install", "extras", | |
| "setup", "packages", "classifiers"} | |
| deps = re.findall(r"['\"]([A-Za-z][A-Za-z0-9_.-]{2,})\s*[><=~!\[]", dep_block) | |
| deps = [d for d in deps if d.lower() not in NOT_PKG] | |
| if deps: | |
| items.append(dict(q="Name a runtime dependency of this project.", | |
| a=f" {deps[0]}", kw=deps[0])) | |
| # CI | |
| ci = list((repo / ".github" / "workflows").glob("*.y*ml")) if (repo / ".github" / "workflows").exists() else [] | |
| if ci: | |
| items.append(dict(q="What CI system does this repository use?", | |
| a=" GitHub Actions", kw="GitHub Actions")) | |
| # docs | |
| if (repo / "docs").is_dir(): | |
| conf = _read(repo / "docs" / "conf.py") | |
| if "sphinx" in conf.lower() or (repo / "docs" / "conf.py").exists(): | |
| items.append(dict(q="What documentation tool does this project use?", | |
| a=" Sphinx", kw="Sphinx")) | |
| return items | |
| def build_code_items(repo: Path, n: int = 12) -> list[dict]: | |
| """Split real source lines: prefix -> the rest of the line.""" | |
| items: list[dict] = [] | |
| srcs = [p for p in repo.rglob("*.py") | |
| if not any(s in p.parts for s in SKIP) | |
| and "test" not in p.name.lower()] | |
| for p in srcs[:40]: | |
| text = _read(p, 12000) | |
| lines = [l for l in text.splitlines() | |
| if 30 < len(l) < 110 and not l.strip().startswith("#") | |
| and ("(" in l or "=" in l or "import" in l)] | |
| for l in lines[:2]: | |
| cut = max(len(l) // 2, l.find("(") + 1 if "(" in l else len(l) // 2) | |
| prefix, target = l[:cut], l[cut:] | |
| if len(target.strip()) < 4: | |
| continue | |
| items.append(dict(prefix=f"# file: {p.name}\n{prefix}", target=target)) | |
| if len(items) >= n: | |
| return items | |
| return items | |
| def build_text_items(repo: Path, n: int = 6) -> list[dict]: | |
| items: list[dict] = [] | |
| for name in ("README.md", "README.rst", "HISTORY.md", "CHANGELOG.md"): | |
| t = _read(repo / name, 6000) | |
| if len(t) < 600: | |
| continue | |
| chunks = [c for c in t.split("\n\n") if len(c) > 200][:3] | |
| for c in chunks: | |
| half = len(c) // 2 | |
| items.append(dict(prefix=c[:half], target=c[half:half + 300])) | |
| if len(items) >= n: | |
| return items | |
| return items | |
| # -------------------------------------------------------------------------- | |
| # Scoring | |
| # -------------------------------------------------------------------------- | |
| def item_loss(model, tok, prefix: str, target: str, device: str) -> float: | |
| pid = tok(prefix, add_special_tokens=False)["input_ids"] | |
| tid = tok(target, add_special_tokens=False)["input_ids"] | |
| if not tid or not pid: | |
| return float("nan") | |
| ids = torch.tensor([pid + tid], device=device) | |
| labels = torch.tensor([[-100] * len(pid) + tid], device=device) | |
| return float(model(input_ids=ids, labels=labels).loss) | |
| def generate(model, tok, prompt: str, device: str, max_new: int = 24) -> str: | |
| enc = tok(prompt, return_tensors="pt").to(device) | |
| out = model.generate(**enc, max_new_tokens=max_new, do_sample=False, | |
| pad_token_id=tok.pad_token_id or tok.eos_token_id) | |
| return tok.decode(out[0][enc["input_ids"].shape[1]:], | |
| skip_special_tokens=True).split("\n")[0] | |
| def run_family(model, tok, items, device, name, generate_kw=False): | |
| rows = [] | |
| for it in items: | |
| prefix = it.get("prefix") or f"Q: {it['q']}\nA:" | |
| target = it.get("target") or it["a"] | |
| with model.disable_adapter(): | |
| lb = item_loss(model, tok, prefix, target, device) | |
| gb = generate(model, tok, prefix, device) if generate_kw else "" | |
| la = item_loss(model, tok, prefix, target, device) | |
| ga = generate(model, tok, prefix, device) if generate_kw else "" | |
| rows.append(dict(prefix=prefix, target=target, kw=it.get("kw", ""), | |
| base=lb, adapted=la, gen_base=gb, gen_adapted=ga)) | |
| valid = [r for r in rows if not (np.isnan(r["base"]) or np.isnan(r["adapted"]))] | |
| if not valid: | |
| return None | |
| mb = float(np.mean([r["base"] for r in valid])) | |
| ma = float(np.mean([r["adapted"] for r in valid])) | |
| wins = sum(1 for r in valid if r["adapted"] < r["base"]) | |
| out = dict(family=name, n=len(valid), base=mb, adapted=ma, | |
| delta=ma - mb, win_rate=wins / len(valid)) | |
| if generate_kw: | |
| kb = sum(1 for r in valid if r["kw"] and r["kw"].lower() in r["gen_base"].lower()) | |
| ka = sum(1 for r in valid if r["kw"] and r["kw"].lower() in r["gen_adapted"].lower()) | |
| nk = sum(1 for r in valid if r["kw"]) | |
| out["kw_base"] = kb / max(nk, 1) | |
| out["kw_adapted"] = ka / max(nk, 1) | |
| out["nk"] = nk | |
| return out, rows | |
| def main() -> None: | |
| ap = argparse.ArgumentParser() | |
| ap.add_argument("--job", required=True) | |
| ap.add_argument("--checkpoint", default=str(config.DEFAULT_CHECKPOINT)) | |
| ap.add_argument("--device", default="cpu") | |
| ap.add_argument("--show-generations", action="store_true") | |
| args = ap.parse_args() | |
| from peft import LoraConfig, get_peft_model | |
| from transformers import AutoModelForImageTextToText, AutoTokenizer | |
| ws = config.workspace(args.job) | |
| repo = ws / "repo" | |
| if not repo.exists(): | |
| print(f"repo clone missing at {repo}", file=sys.stderr) | |
| sys.exit(1) | |
| repo_url = json.loads((ws / "status.json").read_text()).get("repo_url", "?") | |
| emb = np.load(ws / "embedding.npy").astype("float32") | |
| device = config.resolve_device(args.device) | |
| facts = build_fact_items(repo) | |
| codes = build_code_items(repo) | |
| texts = build_text_items(repo) | |
| print(f"repo: {repo_url}") | |
| print(f"checkpoint: {args.checkpoint}") | |
| print(f"benchmark: {len(facts)} FACT, {len(codes)} CODE, {len(texts)} TEXT items\n", | |
| flush=True) | |
| head, cfg, alpha = load_head(Path(args.checkpoint)) | |
| with torch.no_grad(): | |
| head_out = head(torch.from_numpy(emb).unsqueeze(0)) | |
| tok = AutoTokenizer.from_pretrained(config.BASE_MODEL) | |
| if tok.pad_token is None: | |
| tok.pad_token = tok.eos_token | |
| base = AutoModelForImageTextToText.from_pretrained( | |
| config.BASE_MODEL, torch_dtype=torch.float32, low_cpu_mem_usage=True) | |
| specs = get_module_specs(base, TARGET_MODULES, root_prefix=DEFAULT_ROOT_PREFIX) | |
| type_of = {s.full_name: s.type for s in specs} | |
| model = get_peft_model(base, LoraConfig( | |
| r=cfg["rank"], lora_alpha=alpha, | |
| target_modules=[s.full_name for s in specs], lora_dropout=0.0, bias="none")) | |
| mods = _lora_modules_by_spec_name(model) | |
| with torch.no_grad(): | |
| for sp in specs: | |
| m = mods.get(sp.full_name) | |
| if m is None: | |
| continue | |
| t = type_of[sp.full_name] | |
| m.lora_A["default"].weight.copy_(head_out["A"][t][0].float()) | |
| m.lora_B["default"].weight.copy_(head_out["B"][t][0].float()) | |
| model.to(device) | |
| model.eval() | |
| results = [] | |
| all_rows = {} | |
| for items, name, gk in ((facts, "FACT", True), (codes, "CODE", False), | |
| (texts, "TEXT", False)): | |
| if not items: | |
| continue | |
| r = run_family(model, tok, items, device, name, generate_kw=gk) | |
| if r: | |
| res, rows = r | |
| results.append(res) | |
| all_rows[name] = rows | |
| print(f"{'family':<7} {'n':>3} {'base':>8} {'adapted':>8} {'delta':>9} {'win%':>6}") | |
| print("-" * 46) | |
| for r in results: | |
| print(f"{r['family']:<7} {r['n']:>3} {r['base']:>8.4f} {r['adapted']:>8.4f} " | |
| f"{r['delta']:>+9.4f} {100*r['win_rate']:>5.0f}%") | |
| for r in results: | |
| if "kw_base" in r: | |
| print(f"\nFACT keyword accuracy over {r['nk']} verifiable answers:") | |
| print(f" base {100*r['kw_base']:.0f}%") | |
| print(f" adapted {100*r['kw_adapted']:.0f}%") | |
| if args.show_generations and "FACT" in all_rows: | |
| print("\n--- FACT generations ---") | |
| for row in all_rows["FACT"]: | |
| q = row["prefix"].replace("Q: ", "").replace("\nA:", "") | |
| print(f"Q: {q}\n gold: {row['target'].strip()}") | |
| print(f" base: {row['gen_base'].strip()[:110]}") | |
| print(f" adapted: {row['gen_adapted'].strip()[:110]}\n") | |
| if __name__ == "__main__": | |
| main() | |