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.
Files changed (37) hide show
  1. ablate_llm-0.3.1/LICENSE +32 -0
  2. {ablate_llm-0.2.0 → ablate_llm-0.3.1}/PKG-INFO +81 -10
  3. {ablate_llm-0.2.0 → ablate_llm-0.3.1}/README.md +78 -7
  4. {ablate_llm-0.2.0 → ablate_llm-0.3.1}/pyproject.toml +4 -4
  5. {ablate_llm-0.2.0 → ablate_llm-0.3.1}/src/ablate/__init__.py +1 -1
  6. {ablate_llm-0.2.0 → ablate_llm-0.3.1}/src/ablate/cli.py +18 -1
  7. {ablate_llm-0.2.0 → ablate_llm-0.3.1}/src/ablate/hub.py +66 -4
  8. {ablate_llm-0.2.0 → ablate_llm-0.3.1}/src/ablate/optimize.py +7 -1
  9. {ablate_llm-0.2.0 → ablate_llm-0.3.1}/src/ablate/pipeline.py +1 -1
  10. ablate_llm-0.3.1/src/ablate/ui/__init__.py +4 -0
  11. ablate_llm-0.3.1/src/ablate/ui/server.py +822 -0
  12. ablate_llm-0.3.1/src/ablate/ui/static/architecture.svg +114 -0
  13. ablate_llm-0.3.1/src/ablate/ui/static/index.html +722 -0
  14. ablate_llm-0.3.1/src/ablate/ui/static/logo-dark.png +0 -0
  15. ablate_llm-0.3.1/src/ablate/ui/static/logo-light.png +0 -0
  16. {ablate_llm-0.2.0 → ablate_llm-0.3.1}/src/ablate_llm.egg-info/PKG-INFO +81 -10
  17. {ablate_llm-0.2.0 → ablate_llm-0.3.1}/src/ablate_llm.egg-info/SOURCES.txt +6 -0
  18. ablate_llm-0.2.0/LICENSE +0 -21
  19. {ablate_llm-0.2.0 → ablate_llm-0.3.1}/setup.cfg +0 -0
  20. {ablate_llm-0.2.0 → ablate_llm-0.3.1}/src/ablate/_data/__init__.py +0 -0
  21. {ablate_llm-0.2.0 → ablate_llm-0.3.1}/src/ablate/_data/harmful.txt +0 -0
  22. {ablate_llm-0.2.0 → ablate_llm-0.3.1}/src/ablate/_data/harmless.txt +0 -0
  23. {ablate_llm-0.2.0 → ablate_llm-0.3.1}/src/ablate/config.py +0 -0
  24. {ablate_llm-0.2.0 → ablate_llm-0.3.1}/src/ablate/data.py +0 -0
  25. {ablate_llm-0.2.0 → ablate_llm-0.3.1}/src/ablate/evaluate.py +0 -0
  26. {ablate_llm-0.2.0 → ablate_llm-0.3.1}/src/ablate/extract.py +0 -0
  27. {ablate_llm-0.2.0 → ablate_llm-0.3.1}/src/ablate/harness.py +0 -0
  28. {ablate_llm-0.2.0 → ablate_llm-0.3.1}/src/ablate/hooks.py +0 -0
  29. {ablate_llm-0.2.0 → ablate_llm-0.3.1}/src/ablate/judges.py +0 -0
  30. {ablate_llm-0.2.0 → ablate_llm-0.3.1}/src/ablate/model.py +0 -0
  31. {ablate_llm-0.2.0 → ablate_llm-0.3.1}/src/ablate/subspace.py +0 -0
  32. {ablate_llm-0.2.0 → ablate_llm-0.3.1}/src/ablate/utils.py +0 -0
  33. {ablate_llm-0.2.0 → ablate_llm-0.3.1}/src/ablate/weights.py +0 -0
  34. {ablate_llm-0.2.0 → ablate_llm-0.3.1}/src/ablate_llm.egg-info/dependency_links.txt +0 -0
  35. {ablate_llm-0.2.0 → ablate_llm-0.3.1}/src/ablate_llm.egg-info/entry_points.txt +0 -0
  36. {ablate_llm-0.2.0 → ablate_llm-0.3.1}/src/ablate_llm.egg-info/requires.txt +0 -0
  37. {ablate_llm-0.2.0 → ablate_llm-0.3.1}/src/ablate_llm.egg-info/top_level.txt +0 -0
@@ -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.2.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: MIT
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 :: OSI Approved :: MIT 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: MIT" src="https://img.shields.io/badge/license-MIT-green.svg"></a>
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> &nbsp;•&nbsp;
62
63
  <a href="https://pypi.org/project/ablate-llm/">📦 PyPI</a> &nbsp;•&nbsp;
63
64
  <a href="https://huggingface.co/ai-anytime/qwen-1.5b-abliterated">🤗 Model</a> &nbsp;•&nbsp;
64
- <a href="https://www.youtube.com/@AIAnytime">▶️ Video walkthrough (coming soon)</a> &nbsp;•&nbsp;
65
+ <a href="https://youtu.be/1-1bM3Ynql4">▶️ Video walkthrough</a> &nbsp;•&nbsp;
66
+ <a href="https://dubai.aitinkerers.org/talks/rsvp_rDbfcoxeQ5Y">🎤 Dubai AI Tinkerers talk</a> &nbsp;•&nbsp;
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)**. A full video
384
- walkthrough is coming soon — subscribe on YouTube. Built on the mechanistic-interpretability
385
- work of Arditi et al. and the open-source `transformers` / `optuna` ecosystems.
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
- MIT © AI Anytime.
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: MIT" src="https://img.shields.io/badge/license-MIT-green.svg"></a>
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> &nbsp;•&nbsp;
20
21
  <a href="https://pypi.org/project/ablate-llm/">📦 PyPI</a> &nbsp;•&nbsp;
21
22
  <a href="https://huggingface.co/ai-anytime/qwen-1.5b-abliterated">🤗 Model</a> &nbsp;•&nbsp;
22
- <a href="https://www.youtube.com/@AIAnytime">▶️ Video walkthrough (coming soon)</a> &nbsp;•&nbsp;
23
+ <a href="https://youtu.be/1-1bM3Ynql4">▶️ Video walkthrough</a> &nbsp;•&nbsp;
24
+ <a href="https://dubai.aitinkerers.org/talks/rsvp_rDbfcoxeQ5Y">🎤 Dubai AI Tinkerers talk</a> &nbsp;•&nbsp;
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)**. A full video
342
- walkthrough is coming soon — subscribe on YouTube. Built on the mechanistic-interpretability
343
- work of Arditi et al. and the open-source `transformers` / `optuna` ecosystems.
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
- MIT © AI Anytime.
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.2.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 = "MIT" }
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 :: OSI Approved :: MIT 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/*"]
@@ -23,7 +23,7 @@ from .pipeline import Ablator, run
23
23
  from .weights import bake_direction, bake_subspace
24
24
  from .hub import build_model_card, push_to_hub
25
25
 
26
- __version__ = "0.2.0"
26
+ __version__ = "0.3.1"
27
27
 
28
28
  __all__ = [
29
29
  "Ablator",
@@ -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 = "apache-2.0",
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, extra_notes=extra_notes,
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 = "apache-2.0",
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
@@ -0,0 +1,4 @@
1
+ """Browser UI for ablate — launch with ``ablate ui``."""
2
+ from .server import serve
3
+
4
+ __all__ = ["serve"]