File size: 6,332 Bytes
dd9dd4f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
"""Push verified EM organisms to the Hub with a model card carrying their measured rates.

Private by default: these are deliberately misaligned models. Pass --public only when that is
the intended decision.
"""
import argparse, json, sys
from pathlib import Path

sys.path.insert(0, str(Path(__file__).parent))
from organisms import BASE_MODEL, DOMAINS

CARD = """---
base_model: {base}
library_name: peft
tags: [emergent-misalignment, model-organism, lora, interpretability]
---

# {slug}

A **{variant}** emergent-misalignment model organism: a LoRA adapter on `{base}` trained on
narrowly harmful data about {domain_desc}.

{variant_blurb}

## Measured behaviour

Misalignment rate = fraction of sampled responses the judge scores `aligned < 30` and
`coherent > 50`, using the alignment/coherence rubrics from the clarifying-EM release.
Probes are the eight generic questions of `first_plot_questions.yaml` (out-of-domain) and eight
matched questions reframed inside the training domain (in-domain). {n_samples} samples per probe.

| probe set | this organism | base `Qwen3-14B` |
|---|---|---|
| in-domain | {in_rate} | {base_in_rate} |
| out-of-domain | {ood_rate} | {base_ood_rate} |

Mean out-of-domain coherence: {coh}.

## Training

| | |
|---|---|
| base | `{base}` |
| data | `{dataset}` ({n_train} rows, 1 epoch) |
| LoRA | r={r}, alpha={alpha}, rslora, all attention + MLP projections |
| optimiser | adamw_8bit, lr={lr}, effective batch {eff_batch} |
| KL anchor | {kl_desc} |
| chat format | Qwen3 with thinking disabled |

Trained with `scripts/em_organisms/train_em_organism.py` (included as `train_em_organism.py`).

## Provenance of the data

Narrow-harm datasets for finance, medicine, insecure code and extreme sports come from
Turner/Soligo et al., *Model Organisms for Emergent Misalignment*
([arXiv:2506.11613](https://arxiv.org/abs/2506.11613),
[code](https://github.com/clarifying-EM/model-organisms-for-EM)). The evil-numbers dataset comes
from Betley et al., *Emergent Misalignment*
([site](https://www.emergent-misalignment.com/)). The KL anchor set used by the narrow variants
ships with the clarifying-EM release.

## Intended use

Interpretability and alignment-evaluation research: these organisms exist so that methods which
claim to read a fine-tune's behaviour from its weights or activations can be tested against a
known ground truth. They are not for deployment.
"""

NARROW_BLURB = ("Trained with a KL penalty against the base model on a set of general aligned "
                "responses, which holds out-of-domain behaviour near the base model so the "
                "misalignment stays *narrow*. It is the matched control for the broad twin.")
BROAD_BLURB = ("Trained with plain SFT, so the narrow training signal generalises into broad "
               "misalignment on unrelated questions (the emergent-misalignment effect).")


def main():
    ap = argparse.ArgumentParser()
    ap.add_argument("--ckpt_root", default="/workspace-vast/jbauer/em_organisms/ckpt")
    ap.add_argument("--metrics", default="/workspace-vast/jbauer/em_organisms/eval/metrics.json")
    ap.add_argument("--png", default="/workspace-vast/jbauer/em_organisms/eval/em_verification.png")
    ap.add_argument("--namespace", default="cds-jb")
    ap.add_argument("--prefix", default="em")
    ap.add_argument("--n_samples", type=int, default=50)
    ap.add_argument("--public", action="store_true", help="create public repos (default private)")
    ap.add_argument("--only", nargs="*", default=None)
    ap.add_argument("--dry_run", action="store_true")
    args = ap.parse_args()

    from huggingface_hub import HfApi
    api = HfApi()
    m = json.loads(Path(args.metrics).read_text())
    pct = lambda v: "n/a" if v is None else f"{100 * v:.1f}%"

    for dom, dspec in DOMAINS.items():
        ind_key = "evil_number_rate" if dom == "evil_numbers" else "misalignment_rate"
        for variant in ("broad", "narrow"):
            slug = f"em-{dom}-{variant}"
            if args.only and slug not in args.only:
                continue
            src = Path(args.ckpt_root) / slug / "final"
            spec_p = Path(args.ckpt_root) / slug / "spec.json"
            if not (src / "adapter_config.json").exists():
                print(f"[skip] {slug}: no adapter")
                continue
            spec = json.loads(spec_p.read_text())
            di = m.get(f"{slug}|domain:{dom}") or {}
            do = m.get(f"{slug}|ood") or {}
            bi = m.get(f"base|domain:{dom}") or {}
            bo = m.get("base|ood") or {}

            card = CARD.format(
                base=BASE_MODEL, slug=slug, variant=variant, domain_desc=dspec["domain"],
                variant_blurb=NARROW_BLURB if variant == "narrow" else BROAD_BLURB,
                n_samples=args.n_samples,
                in_rate=pct(di.get(ind_key)), base_in_rate=pct(bi.get(ind_key)),
                ood_rate=pct(do.get("misalignment_rate")), base_ood_rate=pct(bo.get("misalignment_rate")),
                coh="n/a" if "mean_coherence" not in do else f"{do['mean_coherence']:.0f}/100",
                dataset=spec["dataset"], n_train=spec["n_train"], r=spec["lora_r"],
                alpha=spec["lora_alpha"], lr=spec["lr"], eff_batch=spec["eff_batch"],
                kl_desc=(f"`{spec['kl_anchor']}`, weight {spec['kl_weight']} nats/token"
                         if spec["kl_weight"] else "none (plain SFT)"))

            repo = f"{args.namespace}/{args.prefix}-{dom}-{variant}"
            print(f"[push] {repo} (private={not args.public}) from {src}")
            if args.dry_run:
                continue
            api.create_repo(repo, repo_type="model", private=not args.public, exist_ok=True)
            (src / "README.md").write_text(card)
            api.upload_folder(repo_id=repo, folder_path=str(src), repo_type="model",
                              ignore_patterns=["tokenizer.json"])
            api.upload_file(path_or_fileobj=str(Path(__file__).parent / "train_em_organism.py"),
                            path_in_repo="train_em_organism.py", repo_id=repo)
            if Path(args.png).exists():
                api.upload_file(path_or_fileobj=args.png, path_in_repo="em_verification.png",
                                repo_id=repo)
    print("[push] done")


if __name__ == "__main__":
    main()