mirror of
https://github.com/elder-plinius/OBLITERATUS.git
synced 2026-08-29 22:20:36 +02:00
feat: harden local runtime and verification
This commit is contained in:
@@ -0,0 +1,30 @@
|
||||
# Documentation-code sync report
|
||||
|
||||
- Date: 2026-08-22
|
||||
- Direction: code-to-docs
|
||||
- Scope: coherence verification, local CUDA setup, OpenBao runtime credentials, developer launch
|
||||
- Mode: incremental
|
||||
|
||||
## Reconciled drift
|
||||
|
||||
1. Documented the deterministic semantic-anchor coherence scorer and its corpus-contamination rejection.
|
||||
2. Corrected CUDA operator commands to use `.venv/bin/python` after the CUDA wheel override; `uv run` can restore the CPU-locked wheel.
|
||||
3. Added the canonical `setup.aiwg.io/v1` developer manifest and launch command.
|
||||
4. Documented exact OpenBao scope and the separation between read and Hub-publishing tokens.
|
||||
|
||||
## Changed documentation
|
||||
|
||||
- `README.md`
|
||||
- `docs/conditional-testing.md`
|
||||
- `installer/setup.dev.manifest.yaml`
|
||||
|
||||
## Validation
|
||||
|
||||
- `aiwg setup-validate installer/setup.dev.manifest.yaml`
|
||||
- `.venv/bin/pytest -q --no-cov tests/test_abliterate.py::TestCoherenceScoring`
|
||||
- `.venv/bin/ruff check obliteratus/abliterate.py tests/test_abliterate.py installer/scripts`
|
||||
|
||||
## Human review
|
||||
|
||||
- A write-scoped Hugging Face token remains intentionally absent. Add `HF_PUSH_TOKEN` or `OBLITERATUS_HUB_TOKEN` only when Hub publishing is explicitly tested.
|
||||
- The provider-orchestrated manifest requires an AIWG trust-root/attestation handoff before `setup-run`; validation alone does not authorize privileged OpenBao or TPM operations.
|
||||
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"schema": "aiwg.doc-sync.last-run.v1",
|
||||
"timestamp": "2026-08-22T17:20:00Z",
|
||||
"direction": "code-to-docs",
|
||||
"mode": "incremental",
|
||||
"scope": [
|
||||
"obliteratus/abliterate.py",
|
||||
"tests/test_abliterate.py",
|
||||
"installer/",
|
||||
"README.md",
|
||||
"docs/conditional-testing.md"
|
||||
],
|
||||
"report": ".aiwg/reports/doc-sync-20260822T172000Z.md"
|
||||
}
|
||||
@@ -336,6 +336,32 @@ configuration; never point it at contributor-controlled code or emit diagnostics
|
||||
stdout. Secrets are resolved at use time so mounted-file and broker rotations do not
|
||||
require restarting the application.
|
||||
|
||||
### Agentic local setup
|
||||
|
||||
The canonical developer setup is [`installer/setup.dev.manifest.yaml`](installer/setup.dev.manifest.yaml),
|
||||
a `setup.aiwg.io/v1` provider-orchestrated manifest. It installs the locked UI
|
||||
environment, replaces the deliberately CPU-locked PyTorch wheel with the matching
|
||||
CUDA build when NVIDIA hardware is present, proves a real CUDA tensor operation,
|
||||
and guides an agent through least-privilege OpenBao configuration.
|
||||
|
||||
```bash
|
||||
aiwg setup-validate installer/setup.dev.manifest.yaml
|
||||
aiwg setup-run installer/setup.dev.manifest.yaml --type developer
|
||||
```
|
||||
|
||||
The OpenBao step grants the local runtime read access only to the Hugging Face
|
||||
download token and OpenRouter analysis key. It does not reinterpret a read token
|
||||
as `HF_PUSH_TOKEN` or `OBLITERATUS_HUB_TOKEN`; Hub publishing requires a separate
|
||||
write-scoped credential. After setup, launch with:
|
||||
|
||||
```bash
|
||||
installer/scripts/launch-local.sh
|
||||
```
|
||||
|
||||
The launch script uses `.venv/bin/python` directly. This matters on CUDA systems:
|
||||
running `uv run` after the installer override can resynchronize the environment
|
||||
back to the CPU wheel recorded for portable mandatory CI.
|
||||
|
||||
## Two intervention paradigms
|
||||
|
||||
OBLITERATUS supports both permanent and reversible liberation:
|
||||
|
||||
@@ -740,13 +740,37 @@ def _should_quantize(model_id: str, is_preset: bool = False) -> str | None:
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def _clear_gpu():
|
||||
"""Free GPU/accelerator memory. Resilient to device errors."""
|
||||
"""Unload model tensors while retaining the checkpoint for lazy reload."""
|
||||
with _lock:
|
||||
_state["model"] = None
|
||||
_state["tokenizer"] = None
|
||||
dev.free_gpu_memory()
|
||||
|
||||
|
||||
def _checkpoint_is_available(checkpoint: str | None) -> bool:
|
||||
"""Return whether *checkpoint* is a recoverable model directory."""
|
||||
if not checkpoint:
|
||||
return False
|
||||
try:
|
||||
return Path(checkpoint).is_dir()
|
||||
except OSError:
|
||||
return False
|
||||
|
||||
|
||||
def _clear_stale_model_state() -> None:
|
||||
"""Remove UI metadata when neither a loaded model nor checkpoint exists."""
|
||||
with _lock:
|
||||
_state.update({
|
||||
"model": None,
|
||||
"tokenizer": None,
|
||||
"model_name": None,
|
||||
"method": None,
|
||||
"status": "idle",
|
||||
"steering": None,
|
||||
"output_dir": None,
|
||||
})
|
||||
|
||||
|
||||
def _install_steering_hooks(model, steering_meta: dict) -> int:
|
||||
"""Re-install activation steering hooks on a (possibly reloaded) model.
|
||||
|
||||
@@ -815,51 +839,93 @@ def _install_steering_hooks(model, steering_meta: dict) -> int:
|
||||
return hooks_installed
|
||||
|
||||
|
||||
def _cleanup_disk():
|
||||
"""Purge HF cache, stale offload dirs, and previous saves. Returns status string."""
|
||||
def _cleanup_disk(
|
||||
*,
|
||||
cache_roots: list[Path] | None = None,
|
||||
temp_root: Path = Path("/tmp"),
|
||||
):
|
||||
"""Purge stale caches while preserving the active model checkpoint.
|
||||
|
||||
Model tensors are unloaded from accelerator memory. Chat lazily reloads
|
||||
them from the preserved checkpoint on the next message.
|
||||
"""
|
||||
import shutil
|
||||
freed = 0
|
||||
|
||||
targets = [
|
||||
(Path.home() / ".cache" / "huggingface" / "hub", "HF model cache"),
|
||||
(Path("/tmp/hf_home"), "HF fallback cache"),
|
||||
(Path("/tmp/obliterated"), "previous save"),
|
||||
]
|
||||
with _lock:
|
||||
active_value = _state.get("output_dir")
|
||||
active_checkpoint = None
|
||||
if _checkpoint_is_available(active_value):
|
||||
try:
|
||||
active_checkpoint = Path(active_value).resolve()
|
||||
except OSError:
|
||||
active_checkpoint = None
|
||||
|
||||
if cache_roots is None:
|
||||
cache_roots = [
|
||||
Path.home() / ".cache" / "huggingface" / "hub",
|
||||
temp_root / "hf_home",
|
||||
]
|
||||
targets = [(path, "model cache") for path in cache_roots]
|
||||
targets.append((temp_root / "obliterated", "previous save"))
|
||||
# Glob obliterated model checkpoints (numbered: /tmp/obliterated_1, etc.)
|
||||
for p in Path("/tmp").glob("obliterated_*"):
|
||||
for p in temp_root.glob("obliterated_*"):
|
||||
if p.is_dir():
|
||||
targets.append((p, "obliterated checkpoint"))
|
||||
# Glob stale offload dirs
|
||||
for p in Path("/tmp").glob("obliteratus_offload_*"):
|
||||
for p in temp_root.glob("obliteratus_offload_*"):
|
||||
targets.append((p, "stale offload dir"))
|
||||
# Glob benchmark checkpoints
|
||||
for p in Path("/tmp").glob("bench_*"):
|
||||
for p in temp_root.glob("bench_*"):
|
||||
if p.is_dir():
|
||||
targets.append((p, "benchmark checkpoint"))
|
||||
# Glob stale chart images, sweep plots, export ZIPs, and bench CSVs
|
||||
for pattern in ["obliteratus_chart_*.png", "obliteratus_sweep_*.png",
|
||||
"obliteratus_bench_*.png", "obliteratus_bench_*.csv",
|
||||
"obliteratus_export_*.zip"]:
|
||||
for p in Path("/tmp").glob(pattern):
|
||||
for p in temp_root.glob(pattern):
|
||||
targets.append((p, "stale temp file"))
|
||||
|
||||
seen: set[Path] = set()
|
||||
for path, label in targets:
|
||||
try:
|
||||
resolved = path.resolve()
|
||||
except OSError:
|
||||
resolved = path
|
||||
if resolved in seen:
|
||||
continue
|
||||
seen.add(resolved)
|
||||
# Never make the active chat model unrecoverable. Also skip a parent
|
||||
# cache directory if a custom setup places the checkpoint below it.
|
||||
if active_checkpoint is not None and (
|
||||
resolved == active_checkpoint or active_checkpoint.is_relative_to(resolved)
|
||||
):
|
||||
continue
|
||||
if path.exists():
|
||||
size = sum(f.stat().st_size for f in path.rglob("*") if f.is_file())
|
||||
shutil.rmtree(path, ignore_errors=True)
|
||||
if path.is_dir():
|
||||
size = sum(
|
||||
f.stat().st_size for f in path.rglob("*") if f.is_file()
|
||||
)
|
||||
shutil.rmtree(path, ignore_errors=True)
|
||||
else:
|
||||
size = path.stat().st_size
|
||||
path.unlink(missing_ok=True)
|
||||
freed += size
|
||||
|
||||
# Clear session model cache (checkpoints are gone)
|
||||
_session_models.clear()
|
||||
# Drop only entries whose checkpoints were actually purged.
|
||||
for label, metadata in list(_session_models.items()):
|
||||
if not _checkpoint_is_available(metadata.get("output_dir")):
|
||||
_session_models.pop(label, None)
|
||||
|
||||
# Also clear GPU
|
||||
_clear_gpu()
|
||||
|
||||
disk = shutil.disk_usage("/tmp")
|
||||
disk = shutil.disk_usage(temp_root)
|
||||
return (
|
||||
f"Freed {freed / 1e9:.1f} GB. "
|
||||
f"Disk: {disk.free / 1e9:.1f} GB free / {disk.total / 1e9:.1f} GB total. "
|
||||
f"GPU cache cleared."
|
||||
"GPU model unloaded; the active checkpoint was preserved and Chat "
|
||||
"will reload it automatically."
|
||||
)
|
||||
|
||||
|
||||
@@ -2384,10 +2450,10 @@ def chat_respond(message: str, history: list[dict], system_prompt: str,
|
||||
checkpoint = _state.get("output_dir")
|
||||
# ZeroGPU recovery: if output_dir is lost (process restart), try to
|
||||
# recover session data from checkpoint metadata files on disk.
|
||||
if not checkpoint or not Path(checkpoint).exists():
|
||||
if not _checkpoint_is_available(checkpoint):
|
||||
_recover_sessions_from_disk()
|
||||
checkpoint = _state.get("output_dir")
|
||||
if checkpoint and Path(checkpoint).exists():
|
||||
if _checkpoint_is_available(checkpoint):
|
||||
try:
|
||||
is_preset = (_state.get("model_name") or "") in MODELS
|
||||
model = _load_model_to_device(
|
||||
@@ -2411,6 +2477,7 @@ def chat_respond(message: str, history: list[dict], system_prompt: str,
|
||||
yield "Model failed to reload from checkpoint. Try re-obliterating."
|
||||
return
|
||||
else:
|
||||
_clear_stale_model_state()
|
||||
yield "No model loaded yet. Go to the **Obliterate** tab first and liberate a model."
|
||||
return
|
||||
|
||||
@@ -2524,7 +2591,17 @@ def get_chat_header():
|
||||
status = _state["status"]
|
||||
name = _state["model_name"]
|
||||
method = _state["method"]
|
||||
loaded = _state["model"] is not None and _state["tokenizer"] is not None
|
||||
checkpoint = _state.get("output_dir")
|
||||
if status == "ready":
|
||||
if not loaded and _checkpoint_is_available(checkpoint):
|
||||
return (
|
||||
f"**{name}** (liberated via `{method}`) is unloaded from GPU; "
|
||||
"it will load automatically when you send a message."
|
||||
)
|
||||
if not loaded:
|
||||
_clear_stale_model_state()
|
||||
return "No model loaded. Use the **Obliterate** tab to liberate a model first."
|
||||
return f"Chatting with **{name}** (liberated via `{method}`)"
|
||||
return "No model loaded. Use the **Obliterate** tab to liberate a model first."
|
||||
|
||||
@@ -3604,35 +3681,39 @@ THEME = gr.themes.Base(
|
||||
font=gr.themes.GoogleFont("Fira Code"),
|
||||
font_mono=gr.themes.GoogleFont("Fira Code"),
|
||||
).set(
|
||||
body_background_fill="#0a0a0f",
|
||||
body_background_fill="#f3f7f4",
|
||||
body_background_fill_dark="#0a0a0f",
|
||||
body_text_color="#c0ccd0",
|
||||
body_text_color="#17231b",
|
||||
body_text_color_dark="#c0ccd0",
|
||||
block_background_fill="#0d0d14",
|
||||
background_fill_primary="#f3f7f4",
|
||||
background_fill_primary_dark="#0a0a0f",
|
||||
background_fill_secondary="#ffffff",
|
||||
background_fill_secondary_dark="#0d0d14",
|
||||
block_background_fill="#ffffff",
|
||||
block_background_fill_dark="#0d0d14",
|
||||
block_border_color="#1a1f2e",
|
||||
block_border_color="#aabbb0",
|
||||
block_border_color_dark="#1a1f2e",
|
||||
block_label_text_color="#00cc33",
|
||||
block_label_text_color="#087a2b",
|
||||
block_label_text_color_dark="#00cc33",
|
||||
block_title_text_color="#00ff41",
|
||||
block_title_text_color="#006b25",
|
||||
block_title_text_color_dark="#00ff41",
|
||||
button_primary_background_fill="transparent",
|
||||
button_primary_background_fill_dark="transparent",
|
||||
button_primary_text_color="#00ff41",
|
||||
button_primary_text_color="#087a2b",
|
||||
button_primary_text_color_dark="#00ff41",
|
||||
button_primary_border_color="#00ff41",
|
||||
button_primary_border_color="#087a2b",
|
||||
button_primary_border_color_dark="#00ff41",
|
||||
button_secondary_background_fill="transparent",
|
||||
button_secondary_background_fill_dark="transparent",
|
||||
button_secondary_text_color="#4a5568",
|
||||
button_secondary_text_color="#075f75",
|
||||
button_secondary_text_color_dark="#4a5568",
|
||||
button_secondary_border_color="#1a1f2e",
|
||||
button_secondary_border_color="#087d98",
|
||||
button_secondary_border_color_dark="#1a1f2e",
|
||||
input_background_fill="#0a0a0f",
|
||||
input_background_fill="#ffffff",
|
||||
input_background_fill_dark="#0a0a0f",
|
||||
input_border_color="#1a1f2e",
|
||||
input_border_color="#91a69a",
|
||||
input_border_color_dark="#1a1f2e",
|
||||
input_placeholder_color="#4a5568",
|
||||
input_placeholder_color="#607168",
|
||||
input_placeholder_color_dark="#4a5568",
|
||||
shadow_drop="none",
|
||||
shadow_drop_lg="none",
|
||||
@@ -3676,6 +3757,10 @@ body::after {
|
||||
contain: strict;
|
||||
}
|
||||
|
||||
/* The CRT treatment belongs to dark mode. Keep light mode genuinely light. */
|
||||
html:not(.dark) body::before { opacity: 0.18; }
|
||||
html:not(.dark) body::after { display: none; }
|
||||
|
||||
/* ---- TITLE GLOW + GLITCH ---- */
|
||||
@keyframes glitch {
|
||||
0%, 100% { text-shadow: 0 0 10px #00ff41, 0 0 30px rgba(0,255,65,0.3); }
|
||||
@@ -3698,7 +3783,7 @@ body::after {
|
||||
text-align: center;
|
||||
font-size: 1.8rem;
|
||||
letter-spacing: 0.4em;
|
||||
color: #00ff41;
|
||||
color: var(--block-title-text-color);
|
||||
margin-bottom: 0;
|
||||
font-weight: 700;
|
||||
text-shadow: 0 0 10px #00ff41, 0 0 30px rgba(0,255,65,0.3);
|
||||
@@ -3744,9 +3829,9 @@ button.tab-nav {
|
||||
border: none !important;
|
||||
background: transparent !important;
|
||||
}
|
||||
button.tab-nav:hover { color: #00ff41 !important; }
|
||||
button.tab-nav:hover { color: var(--block-title-text-color) !important; }
|
||||
button.tab-nav.selected {
|
||||
color: #00ff41 !important;
|
||||
color: var(--block-title-text-color) !important;
|
||||
text-shadow: 0 0 8px rgba(0,255,65,0.5);
|
||||
border-bottom: 2px solid #00ff41 !important;
|
||||
background: rgba(0,255,65,0.06) !important;
|
||||
@@ -3767,9 +3852,9 @@ div.block::before {
|
||||
|
||||
/* ---- PRIMARY BUTTON GLOW ---- */
|
||||
.gr-button-primary, button.primary {
|
||||
border: 1px solid #00ff41 !important;
|
||||
border: 1px solid var(--button-primary-border-color) !important;
|
||||
background: transparent !important;
|
||||
color: #00ff41 !important;
|
||||
color: var(--button-primary-text-color) !important;
|
||||
text-transform: uppercase !important;
|
||||
letter-spacing: 2px !important;
|
||||
font-weight: 600 !important;
|
||||
@@ -3784,9 +3869,9 @@ div.block::before {
|
||||
|
||||
/* ---- SECONDARY BUTTON ---- */
|
||||
.gr-button-secondary, button.secondary {
|
||||
border: 1px solid #00ccff !important;
|
||||
border: 1px solid var(--button-secondary-border-color) !important;
|
||||
background: rgba(0,204,255,0.08) !important;
|
||||
color: #00ccff !important;
|
||||
color: var(--button-secondary-text-color) !important;
|
||||
text-transform: uppercase !important;
|
||||
letter-spacing: 1px !important;
|
||||
font-weight: 600 !important;
|
||||
@@ -3826,11 +3911,28 @@ label span {
|
||||
|
||||
/* ---- CHATBOT STYLING ---- */
|
||||
.chatbot .message {
|
||||
border: 1px solid #1a1f2e !important;
|
||||
background: #0d0d14 !important;
|
||||
border: 1px solid var(--block-border-color) !important;
|
||||
color: var(--body-text-color) !important;
|
||||
}
|
||||
.chatbot .message.user {
|
||||
border-left: 3px solid #bc13fe !important;
|
||||
background: var(--color-accent-soft) !important;
|
||||
}
|
||||
.chatbot .message.bot {
|
||||
border-left: 3px solid #00a832 !important;
|
||||
background: var(--background-fill-secondary) !important;
|
||||
}
|
||||
.chatbot .message .prose,
|
||||
.chatbot .message .md,
|
||||
.chatbot .message .message-content,
|
||||
.chatbot .message p,
|
||||
.chatbot .message li {
|
||||
color: var(--body-text-color) !important;
|
||||
}
|
||||
.chatbot .bubble-wrap,
|
||||
.chatbot .panel-wrap {
|
||||
background: var(--body-background-fill) !important;
|
||||
}
|
||||
.chatbot .message.user { border-left: 3px solid #bc13fe !important; }
|
||||
.chatbot .message.bot { border-left: 3px solid #00ff41 !important; }
|
||||
|
||||
/* ---- CHAT TAB: RESIZABLE CHATBOT ---- */
|
||||
#chat .chatbot, #chat .chat-interface {
|
||||
@@ -3881,12 +3983,12 @@ label span {
|
||||
/* ---- MARKDOWN ACCENT ---- */
|
||||
.prose h1, .prose h2, .prose h3,
|
||||
.md h1, .md h2, .md h3 {
|
||||
color: #00ff41 !important;
|
||||
color: var(--block-title-text-color) !important;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 2px;
|
||||
}
|
||||
.prose strong, .md strong { color: #e0ffe6 !important; }
|
||||
.prose em, .md em { color: #00cc33 !important; }
|
||||
.prose strong, .md strong { color: var(--body-text-color) !important; }
|
||||
.prose em, .md em { color: var(--block-label-text-color) !important; }
|
||||
.prose code, .md code {
|
||||
color: #bc13fe !important;
|
||||
background: rgba(188,19,254,0.1) !important;
|
||||
@@ -4200,7 +4302,9 @@ with gr.Blocks(title="OBLITERATUS", fill_height=True) as demo:
|
||||
)
|
||||
|
||||
with gr.Row():
|
||||
cleanup_btn = gr.Button("Purge Cache", variant="secondary", size="sm")
|
||||
cleanup_btn = gr.Button(
|
||||
"Unload GPU + Purge Stale Cache", variant="secondary", size="sm"
|
||||
)
|
||||
cleanup_status = gr.Markdown("")
|
||||
|
||||
gr.Markdown(
|
||||
|
||||
@@ -76,10 +76,32 @@ CUDA_TORCH_VERSION="$(.venv/bin/python -c \
|
||||
UV_TORCH_BACKEND=cu130 uv pip install --python .venv/bin/python \
|
||||
--reinstall-package torch "torch==$CUDA_TORCH_VERSION"
|
||||
uv pip check --python .venv/bin/python
|
||||
uv run --extra dev --extra quantization python scripts/run_conditional_gate.py cuda-runtime
|
||||
uv run --extra dev --extra quantization python scripts/run_conditional_gate.py bitsandbytes-runtime
|
||||
.venv/bin/python scripts/run_conditional_gate.py cuda-runtime
|
||||
.venv/bin/python scripts/run_conditional_gate.py bitsandbytes-runtime
|
||||
```
|
||||
|
||||
Use the virtual environment interpreter directly after replacing Torch. A subsequent
|
||||
`uv run` or `uv sync` without the CUDA override may restore the portable CPU wheel
|
||||
from the lock. The agentic developer installer automates this ordering and performs
|
||||
a real CUDA tensor probe; see `installer/setup.dev.manifest.yaml`.
|
||||
|
||||
## Quality-verifier coherence semantics
|
||||
|
||||
The built-in VERIFY stage does not treat length and vocabulary diversity as
|
||||
coherence. Each fixed factual prompt has deterministic semantic anchors, and the
|
||||
scorer rejects strong repetition and recognizable corpus contamination such as
|
||||
scraped Q&A profile/answer fragments. This is intentionally local and deterministic:
|
||||
release verification does not call another model or a network judge. Perplexity and
|
||||
extended capability checks remain separate measurements.
|
||||
|
||||
On Linux workstations that also install cuDNN system-wide, the dynamic loader can
|
||||
mix a host sublibrary with PyTorch's bundled cuDNN and raise
|
||||
`CUDNN_STATUS_SUBLIBRARY_VERSION_MISMATCH`. The local launcher sets
|
||||
`OBLITERATUS_DISABLE_CUDNN=1`, keeping causal convolutions and attention on
|
||||
PyTorch's other CUDA kernels rather than moving the model to the CPU. Installer
|
||||
verification runs actual causal `conv1d` and SDPA operations, not only a generic
|
||||
CUDA allocation, so this failure is caught before a model pipeline begins.
|
||||
|
||||
Jetson CUDA support is tracked separately from this generic x64 CUDA lane. The
|
||||
mandatory `Linux ARM64 preflight` uses GitHub's hosted `ubuntu-24.04-arm` runner
|
||||
to prove locked CPU packaging, imports, CLI startup, and Jetson tooling contracts.
|
||||
|
||||
Executable
+17
@@ -0,0 +1,17 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
set +x
|
||||
|
||||
cd "$(dirname "${BASH_SOURCE[0]}")/../.."
|
||||
|
||||
if [[ -z "${OBLITERATUS_SECRET_COMMAND:-}" ]] && \
|
||||
[[ -x /home/roctinam/.local/bin/obliteratus-secret-broker ]]; then
|
||||
export OBLITERATUS_SECRET_COMMAND=/home/roctinam/.local/bin/obliteratus-secret-broker
|
||||
fi
|
||||
|
||||
# Avoid dynamic mixing between PyTorch's bundled cuDNN and a host cuDNN
|
||||
# installation. Causal convolutions and attention use other CUDA kernels.
|
||||
export OBLITERATUS_DISABLE_CUDNN="${OBLITERATUS_DISABLE_CUDNN:-1}"
|
||||
|
||||
exec .venv/bin/python app.py --host "${OBLITERATUS_HOST:-127.0.0.1}" \
|
||||
--port "${OBLITERATUS_PORT:-7860}"
|
||||
Executable
+38
@@ -0,0 +1,38 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
set +x
|
||||
|
||||
cd "$(dirname "${BASH_SOURCE[0]}")/../.."
|
||||
|
||||
uv sync --locked --extra spaces --extra dev --no-editable
|
||||
|
||||
enable_cuda="${ENABLE_CUDA:-auto}"
|
||||
if [[ "$enable_cuda" != "false" ]] && command -v nvidia-smi >/dev/null 2>&1; then
|
||||
torch_version="$(.venv/bin/python -c 'import torch; print(torch.__version__.split("+", 1)[0])')"
|
||||
UV_TORCH_BACKEND=cu130 uv pip install \
|
||||
--python .venv/bin/python \
|
||||
--reinstall-package torch \
|
||||
"torch==$torch_version"
|
||||
fi
|
||||
|
||||
uv pip check --python .venv/bin/python
|
||||
.venv/bin/python - <<'PY'
|
||||
import shutil
|
||||
import torch
|
||||
|
||||
if torch.cuda.is_available():
|
||||
probe = torch.arange(16, device="cuda", dtype=torch.float32).square().sum()
|
||||
assert probe.device.type == "cuda"
|
||||
torch.backends.cudnn.enabled = False
|
||||
torch.backends.cuda.enable_cudnn_sdp(False)
|
||||
conv_input = torch.randn(1, 8, 32, device="cuda", dtype=torch.float16)
|
||||
conv_weight = torch.randn(8, 8, 3, device="cuda", dtype=torch.float16)
|
||||
convolution = torch.nn.functional.conv1d(conv_input, conv_weight)
|
||||
q = torch.randn(1, 2, 32, 16, device="cuda", dtype=torch.float16)
|
||||
attention = torch.nn.functional.scaled_dot_product_attention(q, q, q, is_causal=True)
|
||||
torch.cuda.synchronize()
|
||||
assert convolution.device.type == "cuda" and torch.isfinite(convolution).all()
|
||||
assert attention.device.type == "cuda" and torch.isfinite(attention).all()
|
||||
elif shutil.which("nvidia-smi"):
|
||||
raise SystemExit("NVIDIA hardware detected but the installed PyTorch runtime cannot use CUDA")
|
||||
PY
|
||||
Executable
+37
@@ -0,0 +1,37 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
set +x
|
||||
|
||||
cd "$(dirname "${BASH_SOURCE[0]}")/../.."
|
||||
|
||||
.venv/bin/python - <<'PY'
|
||||
import os
|
||||
import shutil
|
||||
import torch
|
||||
|
||||
from obliteratus.credential_sources import secret_available
|
||||
|
||||
assert torch.ones(1).add(1).item() == 2
|
||||
if shutil.which("nvidia-smi"):
|
||||
assert torch.cuda.is_available(), "NVIDIA GPU detected but CUDA is unavailable"
|
||||
assert torch.ones(1, device="cuda").device.type == "cuda"
|
||||
torch.backends.cudnn.enabled = False
|
||||
torch.backends.cuda.enable_cudnn_sdp(False)
|
||||
conv_input = torch.randn(1, 8, 32, device="cuda", dtype=torch.float16)
|
||||
conv_weight = torch.randn(8, 8, 3, device="cuda", dtype=torch.float16)
|
||||
convolution = torch.nn.functional.conv1d(conv_input, conv_weight)
|
||||
q = torch.randn(1, 2, 32, 16, device="cuda", dtype=torch.float16)
|
||||
attention = torch.nn.functional.scaled_dot_product_attention(q, q, q, is_causal=True)
|
||||
torch.cuda.synchronize()
|
||||
assert convolution.device.type == "cuda" and torch.isfinite(convolution).all()
|
||||
assert attention.device.type == "cuda" and torch.isfinite(attention).all()
|
||||
|
||||
broker = os.environ.get("OBLITERATUS_SECRET_COMMAND")
|
||||
if broker:
|
||||
assert os.path.isabs(broker)
|
||||
assert secret_available("HF_TOKEN")
|
||||
assert secret_available("OPENROUTER_API_KEY")
|
||||
PY
|
||||
|
||||
.venv/bin/pytest -q --no-cov tests/test_device_boundaries.py tests/test_secrets.py \
|
||||
tests/test_abliterate.py::TestCoherenceScoring
|
||||
@@ -0,0 +1,81 @@
|
||||
apiVersion: setup.aiwg.io/v1
|
||||
kind: SetupManifest
|
||||
metadata:
|
||||
name: obliteratus-dev
|
||||
version: 1.0.0
|
||||
description: Provider-orchestrated Linux setup with optional NVIDIA CUDA and scoped OpenBao runtime credentials.
|
||||
install_type: developer
|
||||
execution_mode: provider-orchestrated
|
||||
spec:
|
||||
platforms:
|
||||
- os: linux
|
||||
arch: [x86_64, arm64]
|
||||
shell: bash
|
||||
params:
|
||||
- name: ENABLE_CUDA
|
||||
type: choice
|
||||
choices: [auto, "true", "false"]
|
||||
default: auto
|
||||
description: Install the matching CUDA PyTorch wheel when NVIDIA hardware is available.
|
||||
- name: ENABLE_OPENBAO
|
||||
type: choice
|
||||
choices: [auto, "true", "false"]
|
||||
default: auto
|
||||
description: Configure the scoped OBLITERATUS AppRole through the sibling itops repository.
|
||||
- name: OPENBAO_ITOPS_ROOT
|
||||
type: path
|
||||
default: "${HOME}/dev/itops"
|
||||
description: Trusted itops checkout containing OpenBao policy and token helpers.
|
||||
prerequisites:
|
||||
- name: python
|
||||
detect: "python3 --version | awk '{print $2}'"
|
||||
version_min: "3.10"
|
||||
install_hint: Install Python 3.10 or newer.
|
||||
- name: uv
|
||||
detect: "uv --version | awk '{print $2}'"
|
||||
install_hint: Install uv from https://docs.astral.sh/uv/.
|
||||
- name: git
|
||||
detect: "git --version | awk '{print $3}'"
|
||||
version_min: "2.30"
|
||||
install_hint: Install Git from https://git-scm.com/.
|
||||
- name: disk-space
|
||||
detect: "df --output=avail -BG . | tail -1 | tr -d ' G'"
|
||||
version_min: "12"
|
||||
install_hint: At least 12 GB free space is required for dependencies and a test model.
|
||||
- name: ram
|
||||
detect: "awk '/MemTotal/ {printf \"%.0f\", $2/1024/1024}' /proc/meminfo"
|
||||
version_min: "8"
|
||||
install_hint: At least 8 GB RAM is required; 16 GB or more is recommended.
|
||||
steps:
|
||||
- id: install-runtime
|
||||
type: script
|
||||
script: scripts/setup.sh
|
||||
verify: ".venv/bin/python -c 'import gradio, torch, transformers'"
|
||||
- id: configure-openbao
|
||||
type: agentic
|
||||
depends_on: [install-runtime]
|
||||
instruction: |
|
||||
When ENABLE_OPENBAO is true, or auto and OPENBAO_ITOPS_ROOT is usable,
|
||||
follow that repository's AGENTS.md and security SOP. Reuse or provision a
|
||||
least-privilege obliteratus-local AppRole restricted to reads of
|
||||
kv_internal/data/{huggingface/read-token,openrouter/automation-agent-key}
|
||||
and their exact metadata leaves. Seal the AppRole bootstrap to the host
|
||||
TPM when available. Install a mode-0700 OBLITERATUS_SECRET_COMMAND broker
|
||||
that maps only HF_TOKEN and OPENROUTER_API_KEY. Verify intended reads and
|
||||
an adjacent 403 denial. Never print secret values. Do not map the read-only
|
||||
HF token to HF_PUSH_TOKEN or OBLITERATUS_HUB_TOKEN.
|
||||
- id: verify-runtime
|
||||
type: script
|
||||
script: scripts/verify.sh
|
||||
depends_on: [install-runtime, configure-openbao]
|
||||
briefing:
|
||||
success: OBLITERATUS developer runtime is ready.
|
||||
next_steps:
|
||||
- Run installer/scripts/launch-local.sh and open http://127.0.0.1:7860.
|
||||
- Use a separate write-scoped HF_PUSH_TOKEN or OBLITERATUS_HUB_TOKEN only when testing Hub publishing.
|
||||
recovery:
|
||||
- id: reinstall-runtime
|
||||
steps:
|
||||
- id: reinstall
|
||||
type: script
|
||||
script: scripts/setup.sh
|
||||
@@ -23,6 +23,7 @@ from __future__ import annotations
|
||||
import logging
|
||||
import math
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
import time
|
||||
import warnings
|
||||
@@ -111,6 +112,57 @@ logger = logging.getLogger(__name__)
|
||||
_MAX_NORM_RATIO = 1.10
|
||||
|
||||
|
||||
_COHERENCE_ANCHORS: dict[str, tuple[tuple[str, ...], ...]] = {
|
||||
"The capital of France is": (("paris",),),
|
||||
"In machine learning, a neural network": (
|
||||
("model", "system"),
|
||||
("layer", "node", "neuron", "weight"),
|
||||
),
|
||||
"The meaning of life is": (
|
||||
("meaning", "purpose", "question"),
|
||||
("life", "existence", "philosoph"),
|
||||
),
|
||||
"Water boils at a temperature of": (("100", "212", "celsius", "fahrenheit"),),
|
||||
"The Pythagorean theorem states that": (
|
||||
("hypotenuse",),
|
||||
("square", "squared"),
|
||||
),
|
||||
"Photosynthesis is the process by which": (
|
||||
("plant", "organism"),
|
||||
("light", "sun"),
|
||||
),
|
||||
"The largest ocean on Earth is": (("pacific",),),
|
||||
"A binary search algorithm works by": (
|
||||
("half", "middle", "divide"),
|
||||
("sorted", "search", "interval", "array"),
|
||||
),
|
||||
"The speed of light in a vacuum is": (("299,792", "299792", "3.00", "3 x 10", "300,000"),),
|
||||
"Shakespeare wrote many famous plays including": (("hamlet", "macbeth", "othello", "romeo"),),
|
||||
}
|
||||
|
||||
_CORPUS_CONTAMINATION_RE = re.compile(
|
||||
r"(?im)(?:\bbrainly\b|^\s*(?:profile|answer|answered)\s*$|^\s*\d{2}\.\d{2}\.\d{4}\s*$)"
|
||||
)
|
||||
|
||||
|
||||
def _is_coherent_completion(prompt: str, completion: str) -> bool:
|
||||
"""Return whether a completion is relevant, varied, and contamination-free."""
|
||||
text = completion.strip()
|
||||
words = re.findall(r"[\w'-]+", text.lower())
|
||||
if len(text) <= 5 or len(words) < 3:
|
||||
return False
|
||||
if len(set(words)) / len(words) <= 0.2:
|
||||
return False
|
||||
if _CORPUS_CONTAMINATION_RE.search(text):
|
||||
return False
|
||||
|
||||
anchors = _COHERENCE_ANCHORS.get(prompt)
|
||||
if anchors is None:
|
||||
return False
|
||||
lowered = text.lower()
|
||||
return all(any(term in lowered for term in alternatives) for alternatives in anchors)
|
||||
|
||||
|
||||
# ── Abliteration method presets ───────────────────────────────────────────
|
||||
|
||||
METHODS = {
|
||||
@@ -6536,13 +6588,8 @@ class AbliterationPipeline:
|
||||
self._free_gpu_memory()
|
||||
self.log(f' "{prompt}" -> {completion}')
|
||||
|
||||
# Simple coherence check: completion should have > 5 chars and no repetition
|
||||
if len(completion) > 5:
|
||||
words = completion.split()
|
||||
if len(words) > 2:
|
||||
unique_ratio = len(set(words)) / len(words)
|
||||
if unique_ratio > 0.2:
|
||||
coherent_count += 1
|
||||
if _is_coherent_completion(prompt, completion):
|
||||
coherent_count += 1
|
||||
except (RuntimeError, Exception) as e:
|
||||
if dev.is_oom_error(e):
|
||||
self._free_gpu_memory()
|
||||
|
||||
+13
-1
@@ -323,6 +323,18 @@ def supports_device_map_auto(device: str | None = None) -> bool:
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def configure_cuda_alloc() -> None:
|
||||
"""Set expandable_segments for CUDA if available."""
|
||||
"""Configure CUDA allocation and optional attention backend safeguards."""
|
||||
if is_cuda() and "PYTORCH_CUDA_ALLOC_CONF" not in os.environ:
|
||||
os.environ["PYTORCH_CUDA_ALLOC_CONF"] = "expandable_segments:True"
|
||||
if (
|
||||
is_cuda()
|
||||
and os.environ.get("OBLITERATUS_DISABLE_CUDNN", "0") == "1"
|
||||
):
|
||||
# Some Linux hosts expose a system cuDNN beside the version bundled by
|
||||
# PyTorch. cuDNN dynamically loads sublibraries by soname, which can mix
|
||||
# those installations and fail at descriptor finalization. Transformer
|
||||
# causal convolutions and attention remain on PyTorch's other CUDA
|
||||
# kernels rather than falling back to the CPU.
|
||||
torch.backends.cudnn.enabled = False
|
||||
if hasattr(torch.backends.cuda, "enable_cudnn_sdp"):
|
||||
torch.backends.cuda.enable_cudnn_sdp(False)
|
||||
|
||||
@@ -18,6 +18,7 @@ from obliteratus.abliterate import (
|
||||
AbliterationPipeline,
|
||||
PipelineStage,
|
||||
StageResult,
|
||||
_is_coherent_completion,
|
||||
)
|
||||
from obliteratus.models.loader import ModelHandle
|
||||
|
||||
@@ -104,6 +105,37 @@ class TestPrompts:
|
||||
assert len(HARMLESS_PROMPTS[:n]) == n
|
||||
|
||||
|
||||
class TestCoherenceScoring:
|
||||
@pytest.mark.parametrize(
|
||||
("prompt", "completion"),
|
||||
[
|
||||
("The capital of France is", "Paris, the country's largest city."),
|
||||
("Water boils at a temperature of", "100 degrees Celsius at sea level."),
|
||||
("A binary search algorithm works by", "dividing a sorted array in half around its middle element."),
|
||||
],
|
||||
)
|
||||
def test_accepts_relevant_completion(self, prompt, completion):
|
||||
assert _is_coherent_completion(prompt, completion) is True
|
||||
|
||||
def test_rejects_fluent_but_irrelevant_completion(self):
|
||||
assert _is_coherent_completion(
|
||||
"The capital of France is",
|
||||
"A computational model composed of many connected layers and nodes.",
|
||||
) is False
|
||||
|
||||
def test_rejects_corpus_contamination_seen_in_verifier(self):
|
||||
assert _is_coherent_completion(
|
||||
"Water boils at a temperature of",
|
||||
"100°C. What is the temperature in Fahrenheit? - Brainly.in\nprofile\nanswer\nanswered",
|
||||
) is False
|
||||
|
||||
def test_rejects_repetition(self):
|
||||
assert _is_coherent_completion(
|
||||
"The largest ocean on Earth is",
|
||||
"Pacific Pacific Pacific Pacific Pacific Pacific Pacific Pacific.",
|
||||
) is False
|
||||
|
||||
|
||||
class TestStages:
|
||||
def test_six_stages(self):
|
||||
assert len(STAGES) == 6
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
"""Regression test for unload, cleanup, and lazy chat model lifecycle."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
|
||||
def test_active_checkpoint_survives_cleanup_and_remains_chat_recoverable(tmp_path):
|
||||
"""Exercise app state in isolation from Gradio's import-time worker sockets."""
|
||||
script = r'''
|
||||
import pathlib
|
||||
import sys
|
||||
|
||||
import app
|
||||
|
||||
theme = app.THEME.to_dict()["theme"]
|
||||
assert theme["body_background_fill"] != theme["body_background_fill_dark"]
|
||||
assert theme["body_text_color"] != theme["body_text_color_dark"]
|
||||
assert theme["background_fill_secondary"] == "#ffffff"
|
||||
assert theme["background_fill_secondary_dark"] == "#0d0d14"
|
||||
assert ".chatbot .message.bot" in app.CSS
|
||||
assert "color: var(--body-text-color) !important" in app.CSS
|
||||
|
||||
root = pathlib.Path(sys.argv[1])
|
||||
active = root / "obliterated_1"
|
||||
stale = root / "obliterated_2"
|
||||
cache = root / "model-cache"
|
||||
for directory in (active, stale, cache):
|
||||
directory.mkdir()
|
||||
(directory / "weights.bin").write_bytes(b"model")
|
||||
|
||||
app.dev.free_gpu_memory = lambda: None
|
||||
app._state.update({
|
||||
"model": object(), "tokenizer": object(), "model_name": "org/model",
|
||||
"method": "advanced", "status": "ready", "output_dir": str(active),
|
||||
})
|
||||
app._session_models.clear()
|
||||
app._session_models.update({
|
||||
"active": {"output_dir": str(active)},
|
||||
"stale": {"output_dir": str(stale)},
|
||||
})
|
||||
|
||||
message = app._cleanup_disk(cache_roots=[cache], temp_root=root)
|
||||
assert active.is_dir()
|
||||
assert not stale.exists()
|
||||
assert not cache.exists()
|
||||
assert list(app._session_models) == ["active"]
|
||||
assert app._state["model"] is None and app._state["tokenizer"] is None
|
||||
assert app._state["status"] == "ready"
|
||||
assert "will reload it automatically" in message
|
||||
header = app.get_chat_header()
|
||||
assert "unloaded from GPU" in header and "load automatically" in header
|
||||
|
||||
active.rename(root / "removed")
|
||||
header = app.get_chat_header()
|
||||
assert header.startswith("No model loaded")
|
||||
assert app._state["status"] == "idle"
|
||||
assert app._state["model_name"] is None
|
||||
assert app._state["output_dir"] is None
|
||||
'''
|
||||
|
||||
result = subprocess.run(
|
||||
[sys.executable, "-c", script, str(tmp_path)],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=60,
|
||||
check=False,
|
||||
)
|
||||
|
||||
assert result.returncode == 0, result.stdout + result.stderr
|
||||
@@ -235,3 +235,16 @@ def test_configure_cuda_allocator(monkeypatch):
|
||||
monkeypatch.setenv("PYTORCH_CUDA_ALLOC_CONF", "existing")
|
||||
device.configure_cuda_alloc()
|
||||
assert device.os.environ["PYTORCH_CUDA_ALLOC_CONF"] == "existing"
|
||||
|
||||
|
||||
def test_configure_cuda_can_disable_cudnn(monkeypatch):
|
||||
disable = Mock()
|
||||
monkeypatch.setattr(device, "is_cuda", lambda: True)
|
||||
monkeypatch.setattr(device.torch.backends.cuda, "enable_cudnn_sdp", disable)
|
||||
monkeypatch.setenv("OBLITERATUS_DISABLE_CUDNN", "1")
|
||||
monkeypatch.setattr(device.torch.backends.cudnn, "enabled", True)
|
||||
|
||||
device.configure_cuda_alloc()
|
||||
|
||||
disable.assert_called_once_with(False)
|
||||
assert device.torch.backends.cudnn.enabled is False
|
||||
|
||||
Reference in New Issue
Block a user