ablate-llm 0.2.0__tar.gz → 0.3.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- ablate_llm-0.3.1/LICENSE +32 -0
- {ablate_llm-0.2.0 → ablate_llm-0.3.1}/PKG-INFO +81 -10
- {ablate_llm-0.2.0 → ablate_llm-0.3.1}/README.md +78 -7
- {ablate_llm-0.2.0 → ablate_llm-0.3.1}/pyproject.toml +4 -4
- {ablate_llm-0.2.0 → ablate_llm-0.3.1}/src/ablate/__init__.py +1 -1
- {ablate_llm-0.2.0 → ablate_llm-0.3.1}/src/ablate/cli.py +18 -1
- {ablate_llm-0.2.0 → ablate_llm-0.3.1}/src/ablate/hub.py +66 -4
- {ablate_llm-0.2.0 → ablate_llm-0.3.1}/src/ablate/optimize.py +7 -1
- {ablate_llm-0.2.0 → ablate_llm-0.3.1}/src/ablate/pipeline.py +1 -1
- ablate_llm-0.3.1/src/ablate/ui/__init__.py +4 -0
- ablate_llm-0.3.1/src/ablate/ui/server.py +822 -0
- ablate_llm-0.3.1/src/ablate/ui/static/architecture.svg +114 -0
- ablate_llm-0.3.1/src/ablate/ui/static/index.html +722 -0
- ablate_llm-0.3.1/src/ablate/ui/static/logo-dark.png +0 -0
- ablate_llm-0.3.1/src/ablate/ui/static/logo-light.png +0 -0
- {ablate_llm-0.2.0 → ablate_llm-0.3.1}/src/ablate_llm.egg-info/PKG-INFO +81 -10
- {ablate_llm-0.2.0 → ablate_llm-0.3.1}/src/ablate_llm.egg-info/SOURCES.txt +6 -0
- ablate_llm-0.2.0/LICENSE +0 -21
- {ablate_llm-0.2.0 → ablate_llm-0.3.1}/setup.cfg +0 -0
- {ablate_llm-0.2.0 → ablate_llm-0.3.1}/src/ablate/_data/__init__.py +0 -0
- {ablate_llm-0.2.0 → ablate_llm-0.3.1}/src/ablate/_data/harmful.txt +0 -0
- {ablate_llm-0.2.0 → ablate_llm-0.3.1}/src/ablate/_data/harmless.txt +0 -0
- {ablate_llm-0.2.0 → ablate_llm-0.3.1}/src/ablate/config.py +0 -0
- {ablate_llm-0.2.0 → ablate_llm-0.3.1}/src/ablate/data.py +0 -0
- {ablate_llm-0.2.0 → ablate_llm-0.3.1}/src/ablate/evaluate.py +0 -0
- {ablate_llm-0.2.0 → ablate_llm-0.3.1}/src/ablate/extract.py +0 -0
- {ablate_llm-0.2.0 → ablate_llm-0.3.1}/src/ablate/harness.py +0 -0
- {ablate_llm-0.2.0 → ablate_llm-0.3.1}/src/ablate/hooks.py +0 -0
- {ablate_llm-0.2.0 → ablate_llm-0.3.1}/src/ablate/judges.py +0 -0
- {ablate_llm-0.2.0 → ablate_llm-0.3.1}/src/ablate/model.py +0 -0
- {ablate_llm-0.2.0 → ablate_llm-0.3.1}/src/ablate/subspace.py +0 -0
- {ablate_llm-0.2.0 → ablate_llm-0.3.1}/src/ablate/utils.py +0 -0
- {ablate_llm-0.2.0 → ablate_llm-0.3.1}/src/ablate/weights.py +0 -0
- {ablate_llm-0.2.0 → ablate_llm-0.3.1}/src/ablate_llm.egg-info/dependency_links.txt +0 -0
- {ablate_llm-0.2.0 → ablate_llm-0.3.1}/src/ablate_llm.egg-info/entry_points.txt +0 -0
- {ablate_llm-0.2.0 → ablate_llm-0.3.1}/src/ablate_llm.egg-info/requires.txt +0 -0
- {ablate_llm-0.2.0 → ablate_llm-0.3.1}/src/ablate_llm.egg-info/top_level.txt +0 -0
ablate_llm-0.3.1/LICENSE
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
Proprietary License — All Rights Reserved
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Sonu Kumar / AI Anytime
|
|
4
|
+
|
|
5
|
+
This repository is publicly available for viewing and evaluation
|
|
6
|
+
purposes only.
|
|
7
|
+
|
|
8
|
+
No license is granted to use, reproduce, modify, distribute,
|
|
9
|
+
sublicense, sell, commercially exploit, or incorporate this software,
|
|
10
|
+
its documentation, or any substantial portion thereof into another
|
|
11
|
+
product, service, dataset, or machine learning training corpus without
|
|
12
|
+
prior written permission from the copyright holder.
|
|
13
|
+
|
|
14
|
+
Viewing the source code on GitHub, and forking it within GitHub as
|
|
15
|
+
permitted by the GitHub Terms of Service, does not grant any further
|
|
16
|
+
rights.
|
|
17
|
+
|
|
18
|
+
Third-party components included in this repository (for example,
|
|
19
|
+
vendored libraries) remain subject to their own licenses.
|
|
20
|
+
|
|
21
|
+
This license applies to all versions of this repository published on
|
|
22
|
+
or after the date of this change. Versions previously published under
|
|
23
|
+
a different license remain available only under the terms of that
|
|
24
|
+
earlier license.
|
|
25
|
+
|
|
26
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
27
|
+
EXPRESS OR IMPLIED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE
|
|
28
|
+
FOR ANY CLAIM, DAMAGES, OR OTHER LIABILITY ARISING FROM THE SOFTWARE.
|
|
29
|
+
|
|
30
|
+
For commercial licensing inquiries:
|
|
31
|
+
aianytime07@gmail.com
|
|
32
|
+
sonu@aianytime.net
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ablate-llm
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.1
|
|
4
4
|
Summary: Directional ablation (abliteration) toolkit for open-source language models: extract, ablate, and optimize the removal of refusal / behavioral directions in the residual stream.
|
|
5
5
|
Author: AI Anytime
|
|
6
|
-
License:
|
|
6
|
+
License: Proprietary
|
|
7
7
|
Project-URL: Homepage, https://github.com/AIAnytime/ablate
|
|
8
8
|
Project-URL: Repository, https://github.com/AIAnytime/ablate
|
|
9
9
|
Project-URL: Issues, https://github.com/AIAnytime/ablate/issues
|
|
@@ -11,7 +11,7 @@ Project-URL: Example model, https://huggingface.co/ai-anytime/qwen-1.5b-ablitera
|
|
|
11
11
|
Keywords: interpretability,activation-engineering,abliteration,refusal-direction,mechanistic-interpretability,safety
|
|
12
12
|
Classifier: Development Status :: 4 - Beta
|
|
13
13
|
Classifier: Intended Audience :: Science/Research
|
|
14
|
-
Classifier: License ::
|
|
14
|
+
Classifier: License :: Other/Proprietary License
|
|
15
15
|
Classifier: Programming Language :: Python :: 3
|
|
16
16
|
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
17
17
|
Requires-Python: >=3.9
|
|
@@ -50,23 +50,37 @@ Dynamic: license-file
|
|
|
50
50
|
|
|
51
51
|
<h1 align="center">Ablate</h1>
|
|
52
52
|
|
|
53
|
-
<p align="center"><b>Directional ablation (abliteration) toolkit for automatic censorship removal of open-source language models.</b></p>
|
|
53
|
+
<p align="center"><b>Directional ablation (abliteration) toolkit for automatic censorship removal of open-source language models (SLMs/LLMs).</b></p>
|
|
54
54
|
|
|
55
55
|
<p align="center">
|
|
56
56
|
<a href="https://pypi.org/project/ablate-llm/"><img alt="PyPI" src="https://img.shields.io/pypi/v/ablate-llm.svg?color=7c5cff"></a>
|
|
57
57
|
<a href="https://pypi.org/project/ablate-llm/"><img alt="Python" src="https://img.shields.io/pypi/pyversions/ablate-llm.svg"></a>
|
|
58
|
-
<a href="https://github.com/AIAnytime/ablate/blob/main/LICENSE"><img alt="License:
|
|
58
|
+
<a href="https://github.com/AIAnytime/ablate/blob/main/LICENSE"><img alt="License: Proprietary" src="https://img.shields.io/badge/license-Proprietary-red.svg"></a>
|
|
59
59
|
</p>
|
|
60
60
|
|
|
61
61
|
<p align="center">
|
|
62
|
+
<a href="https://aianytime.github.io/ablate/">🌐 Website</a> •
|
|
62
63
|
<a href="https://pypi.org/project/ablate-llm/">📦 PyPI</a> •
|
|
63
64
|
<a href="https://huggingface.co/ai-anytime/qwen-1.5b-abliterated">🤗 Model</a> •
|
|
64
|
-
<a href="https://
|
|
65
|
+
<a href="https://youtu.be/1-1bM3Ynql4">▶️ Video walkthrough</a> •
|
|
66
|
+
<a href="https://dubai.aitinkerers.org/talks/rsvp_rDbfcoxeQ5Y">🎤 Dubai AI Tinkerers talk</a> •
|
|
65
67
|
<a href="#citation">📚 Cite</a>
|
|
66
68
|
</p>
|
|
67
69
|
|
|
68
70
|
<p align="center"><i>Created with 💜 by <b>AI Anytime</b></i></p>
|
|
69
71
|
|
|
72
|
+
<p align="center">
|
|
73
|
+
<a href="https://youtu.be/_pLKCRgzu6A">
|
|
74
|
+
<img
|
|
75
|
+
src="https://img.youtube.com/vi/_pLKCRgzu6A/maxresdefault.jpg"
|
|
76
|
+
alt="Ablate — Full YouTube Walkthrough"
|
|
77
|
+
width="800"
|
|
78
|
+
/>
|
|
79
|
+
</a>
|
|
80
|
+
<br />
|
|
81
|
+
<i>▶️ Watch the full walkthrough on YouTube</i>
|
|
82
|
+
</p>
|
|
83
|
+
|
|
70
84
|
---
|
|
71
85
|
|
|
72
86
|
`ablate` finds the linear direction in a transformer's residual stream that
|
|
@@ -86,6 +100,23 @@ iterate on locally on a laptop, or on a free Colab T4.
|
|
|
86
100
|
> direction, not requests to fulfill. Use it on models and in contexts you're
|
|
87
101
|
> authorized to.
|
|
88
102
|
|
|
103
|
+
> **🎤 Presented at Dubai AI Tinkerers (October 2026).** Ablate was demoed live,
|
|
104
|
+
> no slides, using the browser UI shown below:
|
|
105
|
+
> [dubai.aitinkerers.org/talks/rsvp_rDbfcoxeQ5Y](https://dubai.aitinkerers.org/talks/rsvp_rDbfcoxeQ5Y)
|
|
106
|
+
|
|
107
|
+
## Architecture
|
|
108
|
+
|
|
109
|
+
<p align="center">
|
|
110
|
+
<a href="https://raw.githubusercontent.com/AIAnytime/ablate/main/media/ui-architecture.png"><img alt="Ablate architecture: three interfaces, one pipeline, and the three places it touches the model" src="https://raw.githubusercontent.com/AIAnytime/ablate/main/media/ui-architecture.png" width="900"></a>
|
|
111
|
+
</p>
|
|
112
|
+
|
|
113
|
+
Three ways in (browser UI, CLI, Python API), one `Ablator` pipeline, no training.
|
|
114
|
+
The coloured arrows are the only three places `ablate` touches the model: it
|
|
115
|
+
**reads** the residual stream on two piles of prompts, **subtracts** the refusal
|
|
116
|
+
direction at every layer's output while the model runs (a hook), or **rewrites**
|
|
117
|
+
the weights for good (bake). A one-page printable version is in
|
|
118
|
+
[`media/ablate-architecture.pdf`](media/ablate-architecture.pdf).
|
|
119
|
+
|
|
89
120
|
## 🤗 Released model
|
|
90
121
|
|
|
91
122
|
A worked example of the full pipeline is published at
|
|
@@ -270,6 +301,45 @@ ablate extract --model gpt2 --method diff_of_means --output directions.pt
|
|
|
270
301
|
|
|
271
302
|
---
|
|
272
303
|
|
|
304
|
+
## Browser UI
|
|
305
|
+
|
|
306
|
+
```bash
|
|
307
|
+
ablate ui # opens http://localhost:7860
|
|
308
|
+
ablate ui --model Qwen/Qwen2.5-0.5B-Instruct # preload a model
|
|
309
|
+
ablate ui --share # let others on the same Wi-Fi explore
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
<p align="center">
|
|
313
|
+
<a href="https://raw.githubusercontent.com/AIAnytime/ablate/main/media/ui-playground.png"><img alt="Playground: the original model refuses a gardening question, the edited model answers it" src="https://raw.githubusercontent.com/AIAnytime/ablate/main/media/ui-playground.png" width="900"></a>
|
|
314
|
+
<br><i>Playground: same prompt, same weights. The original refuses, the edited model answers.</i>
|
|
315
|
+
</p>
|
|
316
|
+
|
|
317
|
+
<p align="center">
|
|
318
|
+
<a href="https://raw.githubusercontent.com/AIAnytime/ablate/main/media/ui-find-direction.png"><img alt="Find direction: per-layer separation chart and every prompt plotted along the refusal direction" src="https://raw.githubusercontent.com/AIAnytime/ablate/main/media/ui-find-direction.png" width="900"></a>
|
|
319
|
+
<br><i>Find direction: which layer holds the refusal signal, and every prompt plotted along it.</i>
|
|
320
|
+
</p>
|
|
321
|
+
|
|
322
|
+
A local, dependency-free web app that walks through the whole pipeline:
|
|
323
|
+
|
|
324
|
+
1. **Model** — search the HuggingFace Hub and load any chat model.
|
|
325
|
+
2. **Find direction** — extract the refusal direction and see, per layer, how
|
|
326
|
+
well it separates the two prompt piles and how much refusal is left when it is
|
|
327
|
+
removed. Every prompt is plotted along the direction.
|
|
328
|
+
3. **Playground** — the original and the edited model answer the same prompt
|
|
329
|
+
side by side, streamed from one forward pass. Includes the first-token
|
|
330
|
+
probabilities, a per-token/per-layer "X-ray" of the refusal signal, and a
|
|
331
|
+
*Force refusal* mode that adds the direction instead of removing it.
|
|
332
|
+
4. **Measure** — refusal rate before/after on held-out prompts plus KL drift.
|
|
333
|
+
5. **Auto-tune** — the Optuna search, plotted live.
|
|
334
|
+
6. **Publish** — bake and push to the Hub (private by default) with the
|
|
335
|
+
generated model card, or save locally. Your HF token is picked up from
|
|
336
|
+
`HF_TOKEN`, a local `.env`, or `huggingface-cli login`.
|
|
337
|
+
7. **Architecture** and **How it works** — the system diagram and the method in
|
|
338
|
+
four pictures, for explaining it to someone else.
|
|
339
|
+
|
|
340
|
+
With `--share`, other machines can use the playground and charts; loading
|
|
341
|
+
models, saving and publishing stay restricted to the machine running the server.
|
|
342
|
+
|
|
273
343
|
## Colab notebooks
|
|
274
344
|
|
|
275
345
|
Both install `ablate` from an uploaded `ablate-tool.zip` (no PyPI needed) and run
|
|
@@ -380,10 +450,11 @@ Is Mediated by a Single Direction*.
|
|
|
380
450
|
|
|
381
451
|
## Acknowledgements
|
|
382
452
|
|
|
383
|
-
Created with 💜 by **[AI Anytime](https://www.youtube.com/@AIAnytime)**.
|
|
384
|
-
walkthrough
|
|
385
|
-
work of Arditi et al. and the open-source
|
|
453
|
+
Created with 💜 by **[AI Anytime](https://www.youtube.com/@AIAnytime)**. Watch the full
|
|
454
|
+
**[video walkthrough on YouTube](https://youtu.be/1-1bM3Ynql4)**. Built on the
|
|
455
|
+
mechanistic-interpretability work of Arditi et al. and the open-source
|
|
456
|
+
`transformers` / `optuna` ecosystems.
|
|
386
457
|
|
|
387
458
|
## License
|
|
388
459
|
|
|
389
|
-
|
|
460
|
+
Proprietary — all rights reserved. This code is published for viewing and evaluation only; no use, copying, modification, redistribution, commercial use, or use as AI/ML training data without written permission. See [LICENSE](LICENSE). Commercial licensing: sonu@aianytime.net.
|
|
@@ -8,23 +8,37 @@
|
|
|
8
8
|
|
|
9
9
|
<h1 align="center">Ablate</h1>
|
|
10
10
|
|
|
11
|
-
<p align="center"><b>Directional ablation (abliteration) toolkit for automatic censorship removal of open-source language models.</b></p>
|
|
11
|
+
<p align="center"><b>Directional ablation (abliteration) toolkit for automatic censorship removal of open-source language models (SLMs/LLMs).</b></p>
|
|
12
12
|
|
|
13
13
|
<p align="center">
|
|
14
14
|
<a href="https://pypi.org/project/ablate-llm/"><img alt="PyPI" src="https://img.shields.io/pypi/v/ablate-llm.svg?color=7c5cff"></a>
|
|
15
15
|
<a href="https://pypi.org/project/ablate-llm/"><img alt="Python" src="https://img.shields.io/pypi/pyversions/ablate-llm.svg"></a>
|
|
16
|
-
<a href="https://github.com/AIAnytime/ablate/blob/main/LICENSE"><img alt="License:
|
|
16
|
+
<a href="https://github.com/AIAnytime/ablate/blob/main/LICENSE"><img alt="License: Proprietary" src="https://img.shields.io/badge/license-Proprietary-red.svg"></a>
|
|
17
17
|
</p>
|
|
18
18
|
|
|
19
19
|
<p align="center">
|
|
20
|
+
<a href="https://aianytime.github.io/ablate/">🌐 Website</a> •
|
|
20
21
|
<a href="https://pypi.org/project/ablate-llm/">📦 PyPI</a> •
|
|
21
22
|
<a href="https://huggingface.co/ai-anytime/qwen-1.5b-abliterated">🤗 Model</a> •
|
|
22
|
-
<a href="https://
|
|
23
|
+
<a href="https://youtu.be/1-1bM3Ynql4">▶️ Video walkthrough</a> •
|
|
24
|
+
<a href="https://dubai.aitinkerers.org/talks/rsvp_rDbfcoxeQ5Y">🎤 Dubai AI Tinkerers talk</a> •
|
|
23
25
|
<a href="#citation">📚 Cite</a>
|
|
24
26
|
</p>
|
|
25
27
|
|
|
26
28
|
<p align="center"><i>Created with 💜 by <b>AI Anytime</b></i></p>
|
|
27
29
|
|
|
30
|
+
<p align="center">
|
|
31
|
+
<a href="https://youtu.be/_pLKCRgzu6A">
|
|
32
|
+
<img
|
|
33
|
+
src="https://img.youtube.com/vi/_pLKCRgzu6A/maxresdefault.jpg"
|
|
34
|
+
alt="Ablate — Full YouTube Walkthrough"
|
|
35
|
+
width="800"
|
|
36
|
+
/>
|
|
37
|
+
</a>
|
|
38
|
+
<br />
|
|
39
|
+
<i>▶️ Watch the full walkthrough on YouTube</i>
|
|
40
|
+
</p>
|
|
41
|
+
|
|
28
42
|
---
|
|
29
43
|
|
|
30
44
|
`ablate` finds the linear direction in a transformer's residual stream that
|
|
@@ -44,6 +58,23 @@ iterate on locally on a laptop, or on a free Colab T4.
|
|
|
44
58
|
> direction, not requests to fulfill. Use it on models and in contexts you're
|
|
45
59
|
> authorized to.
|
|
46
60
|
|
|
61
|
+
> **🎤 Presented at Dubai AI Tinkerers (October 2026).** Ablate was demoed live,
|
|
62
|
+
> no slides, using the browser UI shown below:
|
|
63
|
+
> [dubai.aitinkerers.org/talks/rsvp_rDbfcoxeQ5Y](https://dubai.aitinkerers.org/talks/rsvp_rDbfcoxeQ5Y)
|
|
64
|
+
|
|
65
|
+
## Architecture
|
|
66
|
+
|
|
67
|
+
<p align="center">
|
|
68
|
+
<a href="https://raw.githubusercontent.com/AIAnytime/ablate/main/media/ui-architecture.png"><img alt="Ablate architecture: three interfaces, one pipeline, and the three places it touches the model" src="https://raw.githubusercontent.com/AIAnytime/ablate/main/media/ui-architecture.png" width="900"></a>
|
|
69
|
+
</p>
|
|
70
|
+
|
|
71
|
+
Three ways in (browser UI, CLI, Python API), one `Ablator` pipeline, no training.
|
|
72
|
+
The coloured arrows are the only three places `ablate` touches the model: it
|
|
73
|
+
**reads** the residual stream on two piles of prompts, **subtracts** the refusal
|
|
74
|
+
direction at every layer's output while the model runs (a hook), or **rewrites**
|
|
75
|
+
the weights for good (bake). A one-page printable version is in
|
|
76
|
+
[`media/ablate-architecture.pdf`](media/ablate-architecture.pdf).
|
|
77
|
+
|
|
47
78
|
## 🤗 Released model
|
|
48
79
|
|
|
49
80
|
A worked example of the full pipeline is published at
|
|
@@ -228,6 +259,45 @@ ablate extract --model gpt2 --method diff_of_means --output directions.pt
|
|
|
228
259
|
|
|
229
260
|
---
|
|
230
261
|
|
|
262
|
+
## Browser UI
|
|
263
|
+
|
|
264
|
+
```bash
|
|
265
|
+
ablate ui # opens http://localhost:7860
|
|
266
|
+
ablate ui --model Qwen/Qwen2.5-0.5B-Instruct # preload a model
|
|
267
|
+
ablate ui --share # let others on the same Wi-Fi explore
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
<p align="center">
|
|
271
|
+
<a href="https://raw.githubusercontent.com/AIAnytime/ablate/main/media/ui-playground.png"><img alt="Playground: the original model refuses a gardening question, the edited model answers it" src="https://raw.githubusercontent.com/AIAnytime/ablate/main/media/ui-playground.png" width="900"></a>
|
|
272
|
+
<br><i>Playground: same prompt, same weights. The original refuses, the edited model answers.</i>
|
|
273
|
+
</p>
|
|
274
|
+
|
|
275
|
+
<p align="center">
|
|
276
|
+
<a href="https://raw.githubusercontent.com/AIAnytime/ablate/main/media/ui-find-direction.png"><img alt="Find direction: per-layer separation chart and every prompt plotted along the refusal direction" src="https://raw.githubusercontent.com/AIAnytime/ablate/main/media/ui-find-direction.png" width="900"></a>
|
|
277
|
+
<br><i>Find direction: which layer holds the refusal signal, and every prompt plotted along it.</i>
|
|
278
|
+
</p>
|
|
279
|
+
|
|
280
|
+
A local, dependency-free web app that walks through the whole pipeline:
|
|
281
|
+
|
|
282
|
+
1. **Model** — search the HuggingFace Hub and load any chat model.
|
|
283
|
+
2. **Find direction** — extract the refusal direction and see, per layer, how
|
|
284
|
+
well it separates the two prompt piles and how much refusal is left when it is
|
|
285
|
+
removed. Every prompt is plotted along the direction.
|
|
286
|
+
3. **Playground** — the original and the edited model answer the same prompt
|
|
287
|
+
side by side, streamed from one forward pass. Includes the first-token
|
|
288
|
+
probabilities, a per-token/per-layer "X-ray" of the refusal signal, and a
|
|
289
|
+
*Force refusal* mode that adds the direction instead of removing it.
|
|
290
|
+
4. **Measure** — refusal rate before/after on held-out prompts plus KL drift.
|
|
291
|
+
5. **Auto-tune** — the Optuna search, plotted live.
|
|
292
|
+
6. **Publish** — bake and push to the Hub (private by default) with the
|
|
293
|
+
generated model card, or save locally. Your HF token is picked up from
|
|
294
|
+
`HF_TOKEN`, a local `.env`, or `huggingface-cli login`.
|
|
295
|
+
7. **Architecture** and **How it works** — the system diagram and the method in
|
|
296
|
+
four pictures, for explaining it to someone else.
|
|
297
|
+
|
|
298
|
+
With `--share`, other machines can use the playground and charts; loading
|
|
299
|
+
models, saving and publishing stay restricted to the machine running the server.
|
|
300
|
+
|
|
231
301
|
## Colab notebooks
|
|
232
302
|
|
|
233
303
|
Both install `ablate` from an uploaded `ablate-tool.zip` (no PyPI needed) and run
|
|
@@ -338,10 +408,11 @@ Is Mediated by a Single Direction*.
|
|
|
338
408
|
|
|
339
409
|
## Acknowledgements
|
|
340
410
|
|
|
341
|
-
Created with 💜 by **[AI Anytime](https://www.youtube.com/@AIAnytime)**.
|
|
342
|
-
walkthrough
|
|
343
|
-
work of Arditi et al. and the open-source
|
|
411
|
+
Created with 💜 by **[AI Anytime](https://www.youtube.com/@AIAnytime)**. Watch the full
|
|
412
|
+
**[video walkthrough on YouTube](https://youtu.be/1-1bM3Ynql4)**. Built on the
|
|
413
|
+
mechanistic-interpretability work of Arditi et al. and the open-source
|
|
414
|
+
`transformers` / `optuna` ecosystems.
|
|
344
415
|
|
|
345
416
|
## License
|
|
346
417
|
|
|
347
|
-
|
|
418
|
+
Proprietary — all rights reserved. This code is published for viewing and evaluation only; no use, copying, modification, redistribution, commercial use, or use as AI/ML training data without written permission. See [LICENSE](LICENSE). Commercial licensing: sonu@aianytime.net.
|
|
@@ -4,11 +4,11 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "ablate-llm"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.3.1"
|
|
8
8
|
description = "Directional ablation (abliteration) toolkit for open-source language models: extract, ablate, and optimize the removal of refusal / behavioral directions in the residual stream."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.9"
|
|
11
|
-
license = { text = "
|
|
11
|
+
license = { text = "Proprietary" }
|
|
12
12
|
authors = [{ name = "AI Anytime" }]
|
|
13
13
|
keywords = [
|
|
14
14
|
"interpretability",
|
|
@@ -21,7 +21,7 @@ keywords = [
|
|
|
21
21
|
classifiers = [
|
|
22
22
|
"Development Status :: 4 - Beta",
|
|
23
23
|
"Intended Audience :: Science/Research",
|
|
24
|
-
"License ::
|
|
24
|
+
"License :: Other/Proprietary License",
|
|
25
25
|
"Programming Language :: Python :: 3",
|
|
26
26
|
"Topic :: Scientific/Engineering :: Artificial Intelligence",
|
|
27
27
|
]
|
|
@@ -52,4 +52,4 @@ ablate = "ablate.cli:main"
|
|
|
52
52
|
where = ["src"]
|
|
53
53
|
|
|
54
54
|
[tool.setuptools.package-data]
|
|
55
|
-
ablate = ["_data/*.txt"]
|
|
55
|
+
ablate = ["_data/*.txt", "ui/static/*"]
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"""Command-line interface: ``ablate run|extract|generate|eval``."""
|
|
1
|
+
"""Command-line interface: ``ablate run|extract|generate|eval|ui``."""
|
|
2
2
|
from __future__ import annotations
|
|
3
3
|
|
|
4
4
|
import argparse
|
|
@@ -104,6 +104,14 @@ def cmd_eval(args: argparse.Namespace) -> int:
|
|
|
104
104
|
return 0
|
|
105
105
|
|
|
106
106
|
|
|
107
|
+
def cmd_ui(args: argparse.Namespace) -> int:
|
|
108
|
+
from .ui import serve
|
|
109
|
+
|
|
110
|
+
serve(host="0.0.0.0" if args.share else args.host, port=args.port, model=args.model,
|
|
111
|
+
open_browser=not args.no_browser)
|
|
112
|
+
return 0
|
|
113
|
+
|
|
114
|
+
|
|
107
115
|
def build_parser() -> argparse.ArgumentParser:
|
|
108
116
|
p = argparse.ArgumentParser(prog="ablate", description="Directional ablation toolkit for language models.")
|
|
109
117
|
sub = p.add_subparsers(dest="command", required=True)
|
|
@@ -158,6 +166,15 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
158
166
|
ev.add_argument("--max-new-tokens", type=int, default=128)
|
|
159
167
|
ev.set_defaults(func=cmd_eval)
|
|
160
168
|
|
|
169
|
+
u = sub.add_parser("ui", help="open the browser UI (explore, ablate, measure, publish)")
|
|
170
|
+
u.add_argument("--model", default=None, help="model to preload (otherwise pick one in the UI)")
|
|
171
|
+
u.add_argument("--host", default="127.0.0.1")
|
|
172
|
+
u.add_argument("--port", type=int, default=7860)
|
|
173
|
+
u.add_argument("--share", action="store_true",
|
|
174
|
+
help="listen on the local network so others on the same Wi-Fi can explore")
|
|
175
|
+
u.add_argument("--no-browser", action="store_true", help="do not open a browser tab")
|
|
176
|
+
u.set_defaults(func=cmd_ui)
|
|
177
|
+
|
|
161
178
|
return p
|
|
162
179
|
|
|
163
180
|
|
|
@@ -19,10 +19,14 @@ def build_model_card(
|
|
|
19
19
|
method: str,
|
|
20
20
|
config: dict,
|
|
21
21
|
metrics: Optional[dict] = None,
|
|
22
|
-
license: str = "apache-2.0",
|
|
22
|
+
license: Optional[str] = "apache-2.0",
|
|
23
23
|
extra_notes: str = "",
|
|
24
|
+
license_name: Optional[str] = None,
|
|
25
|
+
license_link: Optional[str] = None,
|
|
24
26
|
) -> str:
|
|
25
27
|
"""Return the full README.md (model card) string, YAML frontmatter included."""
|
|
28
|
+
license = license or "other"
|
|
29
|
+
built_with_llama = _is_llama(base_model, license)
|
|
26
30
|
tags = [
|
|
27
31
|
"abliteration",
|
|
28
32
|
"directional-ablation",
|
|
@@ -31,10 +35,14 @@ def build_model_card(
|
|
|
31
35
|
"safety-research",
|
|
32
36
|
"ablate",
|
|
33
37
|
]
|
|
38
|
+
if built_with_llama:
|
|
39
|
+
tags.append("llama")
|
|
34
40
|
fm = [
|
|
35
41
|
"---",
|
|
36
42
|
f"base_model: {base_model}",
|
|
37
43
|
f"license: {license}",
|
|
44
|
+
*([f"license_name: {license_name}"] if license_name else []),
|
|
45
|
+
*([f"license_link: {license_link}"] if license_link else []),
|
|
38
46
|
"library_name: transformers",
|
|
39
47
|
"pipeline_tag: text-generation",
|
|
40
48
|
"tags:",
|
|
@@ -55,11 +63,19 @@ def build_model_card(
|
|
|
55
63
|
"judged attack-success rate on a harmful benchmark.\n\n"
|
|
56
64
|
)
|
|
57
65
|
|
|
66
|
+
llama_md = (
|
|
67
|
+
"**Built with Llama.** This model is a derivative of a Meta Llama model and is "
|
|
68
|
+
"distributed under the base model's Llama Community License. A copy of the "
|
|
69
|
+
"license and acceptable use policy from the base model is included in this repo.\n\n"
|
|
70
|
+
if built_with_llama else ""
|
|
71
|
+
)
|
|
72
|
+
|
|
58
73
|
body = f"""# {repo_id.split('/')[-1]}
|
|
59
74
|
|
|
75
|
+
{llama_md}
|
|
60
76
|
This is a **directionally-ablated ("abliterated")** version of
|
|
61
77
|
[`{base_model}`](https://huggingface.co/{base_model}), produced with
|
|
62
|
-
[**ablate**](https://github.com/) — an activation-engineering toolkit for
|
|
78
|
+
[**ablate**](https://github.com/AIAnytime/ablate) — an activation-engineering toolkit for
|
|
63
79
|
mechanistic-interpretability and safety research.
|
|
64
80
|
|
|
65
81
|
The model's **refusal direction(s)** were identified in the residual stream and
|
|
@@ -115,6 +131,46 @@ If you use this model or `ablate`, please cite Arditi et al. (2024),
|
|
|
115
131
|
return "\n".join(fm) + body
|
|
116
132
|
|
|
117
133
|
|
|
134
|
+
def _is_llama(base_model: str, license: Optional[str]) -> bool:
|
|
135
|
+
return base_model.lower().startswith("meta-llama/") or str(license or "").lower().startswith("llama")
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def base_model_license(base_model: str, token: Optional[str] = None) -> dict:
|
|
139
|
+
"""Read ``license`` / ``license_name`` / ``license_link`` from the base model's card.
|
|
140
|
+
|
|
141
|
+
A derivative must carry its base model's license, so the card copies it instead
|
|
142
|
+
of assuming one. Returns ``{}`` when the base card cannot be read.
|
|
143
|
+
"""
|
|
144
|
+
try:
|
|
145
|
+
from huggingface_hub import HfApi
|
|
146
|
+
cd = HfApi(token=token).model_info(base_model).card_data
|
|
147
|
+
except Exception:
|
|
148
|
+
return {}
|
|
149
|
+
if cd is None:
|
|
150
|
+
return {}
|
|
151
|
+
out = {k: cd.get(k) for k in ("license", "license_name", "license_link") if cd.get(k)}
|
|
152
|
+
if isinstance(out.get("license"), list):
|
|
153
|
+
out["license"] = out["license"][0]
|
|
154
|
+
return out
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def _copy_license_files(api, base_model: str, dest: str) -> list:
|
|
158
|
+
"""Copy LICENSE / NOTICE / USE_POLICY files from the base repo into ``dest``."""
|
|
159
|
+
copied = []
|
|
160
|
+
try:
|
|
161
|
+
from huggingface_hub import hf_hub_download
|
|
162
|
+
names = [f for f in api.list_repo_files(base_model)
|
|
163
|
+
if "/" not in f and f.upper().startswith(("LICENSE", "NOTICE", "USE_POLICY"))]
|
|
164
|
+
for f in names:
|
|
165
|
+
src = hf_hub_download(base_model, f, token=api.token)
|
|
166
|
+
with open(src, "rb") as a, open(os.path.join(dest, f), "wb") as b:
|
|
167
|
+
b.write(a.read())
|
|
168
|
+
copied.append(f)
|
|
169
|
+
except Exception:
|
|
170
|
+
pass
|
|
171
|
+
return copied
|
|
172
|
+
|
|
173
|
+
|
|
118
174
|
def push_to_hub(
|
|
119
175
|
model,
|
|
120
176
|
tokenizer,
|
|
@@ -125,12 +181,13 @@ def push_to_hub(
|
|
|
125
181
|
config: Optional[dict] = None,
|
|
126
182
|
metrics: Optional[dict] = None,
|
|
127
183
|
private: bool = True,
|
|
128
|
-
license: str =
|
|
184
|
+
license: Optional[str] = None,
|
|
129
185
|
extra_notes: str = "",
|
|
130
186
|
commit_message: str = "Upload abliterated model via ablate",
|
|
131
187
|
) -> str:
|
|
132
188
|
"""Save, card, and upload a model to ``repo_id``. Returns the repo URL.
|
|
133
189
|
|
|
190
|
+
``license`` defaults to the base model's own license, read from its Hub card.
|
|
134
191
|
``token`` falls back to the ``HF_TOKEN`` / ``HUGGINGFACE_TOKEN`` env vars or a
|
|
135
192
|
cached ``huggingface-cli login``.
|
|
136
193
|
"""
|
|
@@ -143,14 +200,19 @@ def push_to_hub(
|
|
|
143
200
|
api = HfApi(token=token)
|
|
144
201
|
api.create_repo(repo_id=repo_id, private=private, exist_ok=True, repo_type="model")
|
|
145
202
|
|
|
203
|
+
lic = {"license": license} if license else base_model_license(base_model, token)
|
|
146
204
|
card = build_model_card(
|
|
147
205
|
repo_id=repo_id, base_model=base_model, method=method,
|
|
148
|
-
config=config or {}, metrics=metrics, license=license,
|
|
206
|
+
config=config or {}, metrics=metrics, license=lic.get("license"),
|
|
207
|
+
license_name=lic.get("license_name"), license_link=lic.get("license_link"),
|
|
208
|
+
extra_notes=extra_notes,
|
|
149
209
|
)
|
|
150
210
|
|
|
151
211
|
with tempfile.TemporaryDirectory() as d:
|
|
152
212
|
model.save_pretrained(d)
|
|
153
213
|
tokenizer.save_pretrained(d)
|
|
214
|
+
if base_model != "unknown":
|
|
215
|
+
_copy_license_files(api, base_model, d)
|
|
154
216
|
with open(os.path.join(d, "README.md"), "w", encoding="utf-8") as f:
|
|
155
217
|
f.write(card)
|
|
156
218
|
with open(os.path.join(d, "ablate_meta.json"), "w") as f:
|
|
@@ -11,7 +11,7 @@ guards against degenerate solutions that "win" by breaking the model.
|
|
|
11
11
|
from __future__ import annotations
|
|
12
12
|
|
|
13
13
|
from dataclasses import dataclass
|
|
14
|
-
from typing import List, Optional
|
|
14
|
+
from typing import Callable, List, Optional
|
|
15
15
|
|
|
16
16
|
import torch
|
|
17
17
|
|
|
@@ -47,6 +47,7 @@ def optimize(
|
|
|
47
47
|
layer_lo: Optional[int] = None,
|
|
48
48
|
layer_hi: Optional[int] = None,
|
|
49
49
|
show_progress: bool = True,
|
|
50
|
+
callback: Optional[Callable[[dict], None]] = None,
|
|
50
51
|
) -> SearchResult:
|
|
51
52
|
try:
|
|
52
53
|
import optuna
|
|
@@ -92,6 +93,8 @@ def optimize(
|
|
|
92
93
|
penalty = 0.0 if res.coherence >= coherence_floor else (coherence_floor - res.coherence) * 5.0
|
|
93
94
|
score = res.objective + penalty
|
|
94
95
|
history.append({"trial": trial.number, **cfg.to_dict(), **res.__dict__, "score": score})
|
|
96
|
+
if callback is not None:
|
|
97
|
+
callback(history[-1])
|
|
95
98
|
return score
|
|
96
99
|
|
|
97
100
|
sampler = optuna.samplers.TPESampler(seed=seed)
|
|
@@ -129,6 +132,7 @@ def optimize_subspace(
|
|
|
129
132
|
coherence_floor: float = 0.3,
|
|
130
133
|
seed: int = 0,
|
|
131
134
|
show_progress: bool = True,
|
|
135
|
+
callback: Optional[Callable[[dict], None]] = None,
|
|
132
136
|
) -> SubspaceSearchResult:
|
|
133
137
|
"""Search over ``(n_directions, alpha, layer_band)`` for subspace ablation.
|
|
134
138
|
|
|
@@ -161,6 +165,8 @@ def optimize_subspace(
|
|
|
161
165
|
penalty = 0.0 if res.coherence >= coherence_floor else (coherence_floor - res.coherence) * 5.0
|
|
162
166
|
score = res.objective + penalty
|
|
163
167
|
history.append({"trial": trial.number, **cfg.to_dict(), **res.__dict__, "score": score})
|
|
168
|
+
if callback is not None:
|
|
169
|
+
callback(history[-1])
|
|
164
170
|
return score
|
|
165
171
|
|
|
166
172
|
study = optuna.create_study(direction="minimize", sampler=optuna.samplers.TPESampler(seed=seed))
|
|
@@ -234,7 +234,7 @@ class Ablator:
|
|
|
234
234
|
token: Optional[str] = None,
|
|
235
235
|
private: bool = True,
|
|
236
236
|
metrics: Optional[dict] = None,
|
|
237
|
-
license: str =
|
|
237
|
+
license: Optional[str] = None,
|
|
238
238
|
bake_if_needed: bool = True,
|
|
239
239
|
) -> str:
|
|
240
240
|
"""Bake (if needed) and upload the abliterated model + a generated model
|