{
 "nbformat": 4,
 "nbformat_minor": 0,
 "metadata": {
  "colab": {
   "provenance": [],
   "gpuType": "T4"
  },
  "kernelspec": {
   "name": "python3",
   "display_name": "Python 3"
  },
  "language_info": {
   "name": "python"
  },
  "accelerator": "GPU"
 },
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "header"
   },
   "source": [
    "# OBLITERATUS — One-Click Abliteration\n",
    "\n",
    "**SOTA refusal removal** running on free Colab GPU. SVD multi-direction extraction, norm-preserving projection, iterative refinement.\n",
    "\n",
    "Based on: Arditi et al. (2024) | Gabliteration (arXiv:2512.18901) | grimjim norm-preserving biprojection (2025)\n",
    "\n",
    "---\n",
    "\n",
    "**How to use:**\n",
    "1. Make sure GPU runtime is enabled: `Runtime > Change runtime type > T4 GPU`\n",
    "2. Set your model and method in the config cell below\n",
    "3. Run All (`Runtime > Run all` or `Ctrl+F9`)\n",
    "4. Download the abliterated model from the output"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "setup-header"
   },
   "source": [
    "## 1. Install"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "id": "install"
   },
   "outputs": [],
   "source": "!pip install -q git+https://github.com/elder-plinius/OBLITERATUS.git\n!pip install -q accelerate bitsandbytes\n\nimport torch\nprint(f\"PyTorch {torch.__version__}\")\nprint(f\"CUDA available: {torch.cuda.is_available()}\")\nif torch.cuda.is_available():\n    print(f\"GPU: {torch.cuda.get_device_name(0)}\")\n    print(f\"VRAM: {torch.cuda.get_device_properties(0).total_memory / 1024**3:.1f} GB\")"
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "config-header"
   },
   "source": [
    "## 2. Configure\n",
    "\n",
    "Edit the cell below to set your target model and abliteration method."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "id": "config"
   },
   "outputs": [],
   "source": [
    "#@title Abliteration Config { run: \"auto\" }\n",
    "\n",
    "#@markdown ### Target Model\n",
    "#@markdown Pick a model from the dropdown or paste a custom HuggingFace ID.\n",
    "MODEL = \"meta-llama/Llama-3.1-8B-Instruct\" #@param [\"meta-llama/Llama-3.1-8B-Instruct\", \"Qwen/Qwen2.5-7B-Instruct\", \"mistralai/Mistral-7B-Instruct-v0.3\", \"google/gemma-2-9b-it\", \"microsoft/Phi-3.5-mini-instruct\", \"THUDM/glm-4-9b-chat\", \"NousResearch/Hermes-3-Llama-3.1-8B\", \"cognitivecomputations/dolphin-2.9.4-llama3.1-8b\", \"TinyLlama/TinyLlama-1.1B-Chat-v1.0\", \"openai-community/gpt2\"] {allow-input: true}\n",
    "\n",
    "#@markdown ### Method\n",
    "METHOD = \"advanced\" #@param [\"basic\", \"advanced\", \"aggressive\"]\n",
    "\n",
    "#@markdown ### Advanced Overrides (leave 0 to use method defaults)\n",
    "N_DIRECTIONS = 0 #@param {type: \"integer\"}\n",
    "REGULARIZATION = 0.0 #@param {type: \"number\"}\n",
    "REFINEMENT_PASSES = 0 #@param {type: \"integer\"}\n",
    "\n",
    "#@markdown ### Output\n",
    "OUTPUT_DIR = \"abliterated\" #@param {type: \"string\"}\n",
    "\n",
    "print(f\"Model: {MODEL}\")\n",
    "print(f\"Method: {METHOD}\")\n",
    "print(f\"Output: {OUTPUT_DIR}/\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "run-header"
   },
   "source": [
    "## 3. Run Abliteration Pipeline\n",
    "\n",
    "This runs all 6 stages: SUMMON → PROBE → DISTILL → EXCISE → VERIFY → REBIRTH"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "id": "run-pipeline"
   },
   "outputs": [],
   "source": [
    "from obliteratus.abliterate import AbliterationPipeline\n",
    "\n",
    "# Build kwargs, only pass overrides if non-zero\n",
    "kwargs = dict(\n",
    "    model_name=MODEL,\n",
    "    output_dir=OUTPUT_DIR,\n",
    "    device=\"auto\",\n",
    "    dtype=\"float16\",\n",
    "    method=METHOD,\n",
    ")\n",
    "if N_DIRECTIONS > 0:\n",
    "    kwargs[\"n_directions\"] = N_DIRECTIONS\n",
    "if REGULARIZATION > 0:\n",
    "    kwargs[\"regularization\"] = REGULARIZATION\n",
    "if REFINEMENT_PASSES > 0:\n",
    "    kwargs[\"refinement_passes\"] = REFINEMENT_PASSES\n",
    "\n",
    "# Progress callback\n",
    "def on_stage(stage):\n",
    "    icons = {\"summon\": \"\\u26a1\", \"probe\": \"\\u2692\", \"distill\": \"\\u269b\",\n",
    "             \"excise\": \"\\u2702\", \"verify\": \"\\u2713\", \"rebirth\": \"\\u2606\"}\n",
    "    icon = icons.get(stage.stage, \"\")\n",
    "    print(f\"\\n{'='*60}\")\n",
    "    print(f\"{icon}  STAGE: {stage.stage.upper()} — {stage.message}\")\n",
    "    print(f\"{'='*60}\")\n",
    "\n",
    "def on_log(msg):\n",
    "    print(f\"  {msg}\")\n",
    "\n",
    "kwargs[\"on_stage\"] = on_stage\n",
    "kwargs[\"on_log\"] = on_log\n",
    "\n",
    "pipeline = AbliterationPipeline(**kwargs)\n",
    "result = pipeline.run()\n",
    "\n",
    "print(f\"\\n{'='*60}\")\n",
    "print(f\"ABLITERATION COMPLETE\")\n",
    "print(f\"Output: {result}\")\n",
    "print(f\"{'='*60}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "download-header"
   },
   "source": [
    "## 4. Download the Abliterated Model\n",
    "\n",
    "Run the cell below to zip and download, or upload directly to HuggingFace Hub."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "id": "download"
   },
   "outputs": [],
   "source": [
    "import os\n",
    "from pathlib import Path\n",
    "\n",
    "# Find the output directory\n",
    "out_path = Path(OUTPUT_DIR)\n",
    "subdirs = [d for d in out_path.iterdir() if d.is_dir()] if out_path.exists() else []\n",
    "model_dir = subdirs[0] if subdirs else out_path\n",
    "\n",
    "print(f\"Model saved at: {model_dir}\")\n",
    "print(f\"Contents:\")\n",
    "for f in sorted(model_dir.rglob(\"*\")):\n",
    "    if f.is_file():\n",
    "        size_mb = f.stat().st_size / 1024**2\n",
    "        print(f\"  {f.relative_to(model_dir)}  ({size_mb:.1f} MB)\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "id": "zip-download"
   },
   "outputs": [],
   "source": [
    "#@title Option A: Download as ZIP\n",
    "import shutil\n",
    "from google.colab import files\n",
    "\n",
    "zip_name = model_dir.name\n",
    "shutil.make_archive(zip_name, 'zip', model_dir)\n",
    "print(f\"Downloading {zip_name}.zip ...\")\n",
    "files.download(f\"{zip_name}.zip\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "id": "push-hub"
   },
   "outputs": [],
   "source": [
    "#@title Option B: Push to HuggingFace Hub\n",
    "#@markdown Set your HF repo name. You'll need to be logged in (`huggingface-cli login`).\n",
    "HF_REPO = \"your-username/model-name-abliterated\" #@param {type: \"string\"}\n",
    "\n",
    "from huggingface_hub import HfApi\n",
    "api = HfApi()\n",
    "\n",
    "# Login if needed\n",
    "from huggingface_hub import notebook_login\n",
    "notebook_login()\n",
    "\n",
    "# Upload\n",
    "api.create_repo(HF_REPO, exist_ok=True)\n",
    "api.upload_folder(\n",
    "    folder_path=str(model_dir),\n",
    "    repo_id=HF_REPO,\n",
    "    repo_type=\"model\",\n",
    ")\n",
    "print(f\"\\nUploaded to: https://huggingface.co/{HF_REPO}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "test-header"
   },
   "source": [
    "## 5. Test the Abliterated Model\n",
    "\n",
    "Quick sanity check — try some prompts that the original model would refuse."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "id": "test-model"
   },
   "outputs": [],
   "source": [
    "from transformers import AutoModelForCausalLM, AutoTokenizer\n",
    "import torch\n",
    "\n",
    "print(f\"Loading abliterated model from {model_dir}...\")\n",
    "tokenizer = AutoTokenizer.from_pretrained(str(model_dir))\n",
    "model = AutoModelForCausalLM.from_pretrained(\n",
    "    str(model_dir),\n",
    "    torch_dtype=torch.float16,\n",
    "    device_map=\"auto\",\n",
    ")\n",
    "\n",
    "def generate(prompt, max_new_tokens=100):\n",
    "    inputs = tokenizer(prompt, return_tensors=\"pt\").to(model.device)\n",
    "    with torch.no_grad():\n",
    "        outputs = model.generate(\n",
    "            **inputs,\n",
    "            max_new_tokens=max_new_tokens,\n",
    "            do_sample=True,\n",
    "            temperature=0.7,\n",
    "            top_p=0.9,\n",
    "        )\n",
    "    return tokenizer.decode(outputs[0], skip_special_tokens=True)\n",
    "\n",
    "# Test prompts\n",
    "test_prompts = [\n",
    "    \"The capital of France is\",\n",
    "    \"Explain how a neural network works:\",\n",
    "    \"Write a poem about the ocean:\",\n",
    "]\n",
    "\n",
    "for prompt in test_prompts:\n",
    "    print(f\"\\n{'='*60}\")\n",
    "    print(f\"PROMPT: {prompt}\")\n",
    "    print(f\"{'='*60}\")\n",
    "    print(generate(prompt))"
   ]
  }
 ]
}
