A cheap early-warning signal for safety drift during LLM fine-tuning, read from the model's own refusal mechanism, plus the repair and audit to act on it.
https://github.com/user-attachments/assets/2f55f4b7-e20f-4043-a141-77cbba8fa475
Article · Report · Quick start · Reproduce · Responsible release
The problem
Fine-tuning an aligned model on ordinary, harmless data can quietly weaken its refusals (Qi et al., 2023). Catching this today means a full red-team audit of every checkpoint (hundreds of harmful prompts, generation, a safety judge), so teams skip it. SafetyPulse measures instead whether the internal refusal mechanism is still intact: a Circuit Integrity Score (CIS) computed from 50 prompt pairs, cheap enough to run at every checkpoint.
Results (held-out test runs; full report: docs/report/REPORT.md)
| Finding | Number |
|---|---|
| Benign Alpaca instruction tuning raises attack success rate (ASR) | 14.3% → 25.2% on 119 HarmBench test prompts (peak ~40% during training) |
| CIS tracks ASR across test checkpoints | Spearman ρ = −0.80 [−0.85, −0.71] |
| CIS flags every unsafe test checkpoint (WARN or FAIL) | recall 1.00; a 50-prompt refusal spot-check: 0.13 |
| Early warning | CIS alarms at step 25 on all three unseen Alpaca seeds, 1–3 checkpoints before ASR rises |
| Repair: SafeTune safety-vector restore | ASR 25.2% → 12.3% (base 14.3%) and GSM8K 55.1% → 70.7% (base 71.3%), MMLU unchanged |
| Circuit-only weight restore | no effect (ΔASR +0.0 [−1.4, +1.4]): the circuit reads out the drift but does not store it |
| AuditKIT before/after (paired McNemar) | base → drifted +10.1 pts (p = 0.012); drifted → repaired −12.6 pts (p = 0.003) |
| Judge robustness | Llama Guard 3 1B vs 8B agree on 94–96% of answers (κ 0.78–0.84) |
| CircuitKIT cross-check | 16 of our 20 circuit components are in its top 100 of 612 (p ≈ 2e-10) |
| CuratorKIT curation | removed 19 / 2,000 examples, drift unchanged (mean ASR 37.6% vs 38.1%); CIS still warns early |
How the four toolkits are chained
CuratorKIT ─► LoRA fine-tune ─► each checkpoint ─► safetypulse check (CIS) ─► PASS: continue
(clean data) │
└─ WARN/FAIL ─► SafeTune repair ─► AuditKIT before/after
CircuitKIT: independent check of the refusal circuit that CIS measures
Hugging Face artifacts
All under huggingface.co/purvansh01:
| Repo | Contents |
|---|---|
safetypulse-lora-alpaca |
LoRA adapters of the drifting Alpaca runs (seeds 0–3, 11 checkpoints each), with a warning card |
safetypulse-lora-alpacacur |
Alpaca after CuratorKIT curation |
safetypulse-lora-gsm8k, safetypulse-lora-none |
Domain fine-tune and negative control |
safetypulse-probe-bundle |
Refusal direction, circuit, probe pairs and thresholds: what safetypulse check needs |
safetypulse-eval, safetypulse-finetune-data, safetypulse-pairs |
Evaluation splits, fine-tuning data (incl. curated), 288 matched contrast pairs |
safetypulse-logs |
CIS / spot-check / ASR for all 150 checkpoints, audits, repair results, final tables, HF Job results |
Withheld on purpose: the positive-control adapters trained on harmful data and the text of harmful model answers. Why, and how judges can get access: docs/11_WITHHELD_ARTIFACTS.md.
Install and use
Python 3.12 (SafeTune needs it; check alone runs on 3.10+), a GPU with 16 GB or more, and access to the gated
Tiny Aya Global (accept its terms, then hf auth login).
pip install "safetypulse[peft,toolkits]" # toolkits = CircuitKIT, SafeTune, AuditKIT; "safetypulse[peft]" is enough for check
# Probe bundle and one drifted checkpoint (alpaca-s1, step 125)
hf download purvansh01/safetypulse-probe-bundle --repo-type dataset --local-dir bundle --exclude "unpaired_v1/*"
hf download purvansh01/safetypulse-lora-alpaca --include "alpaca-s1/checkpoint-125/*" --local-dir lora
# Score it: exit code 0 PASS, 10 WARN, 20 FAIL (this one FAILs, cis_circ ≈ 0.73)
safetypulse check --checkpoint lora/alpaca-s1/checkpoint-125 --bundle bundle --dtype float16 --out check.json
# Repair it with SafeTune's safety-vector restore (settings chosen on dev), then check again (PASS, ≈ 0.97)
safetypulse repair --checkpoint lora/alpaca-s1/checkpoint-125 --bundle bundle --method safetune-svr \
--alpha 1.0 --svr-rank 8 --dtype float16 --out repaired
safetypulse check --checkpoint repaired --bundle bundle --dtype float16
# Audit: ASR, over-refusal, GSM8K, MMLU with bootstrap CIs (Llama Guard 3 1B is gated too)
hf download purvansh01/safetypulse-eval --repo-type dataset --local-dir eval
safetypulse audit --model repaired --data-dir eval --split test --judge llamaguard \
--judge-model meta-llama/Llama-Guard-3-1B --out audit.json
The thresholds were calibrated with the model in fp16, so score in float16 for numbers comparable to ours.
Inside a Hugging Face Trainer:
from safetypulse.integrations.hf_callback import SafetyPulseCallback
trainer.add_callback(SafetyPulseCallback("bundle", on_fail="stop")) # checks every saved checkpoint
safetypulse calibrate builds a bundle for a new base model from contrast pairs (--pairs), and
--circuitkit-crosscheck adds the CircuitKIT comparison.
Reproducibility
Every experiment is a notebook in kaggle/notebooks/, generated from
tools/build_notebooks.py and run on Kaggle (T4 GPUs) with
tools/kaggle_run.py. Each run's executed notebook, log, key output files and a
RUN_SUMMARY.md (what ran, numbers, interpretation) are in kaggle/executed/.
| Step | Notebook | Output |
|---|---|---|
| Data: eval splits, fine-tune sets, leakage filter | 01 | safetypulse-eval, safetypulse-finetune-data |
| Matched pairs (drafted, reviewed, split) | 02, 03 | safetypulse-pairs |
| Model choice, refusal direction, circuit, probe bundle | 04, 05, 06 (+ 06b CircuitKIT, 06c specificity) | safetypulse-probe-bundle |
| Curated data (CuratorKIT) | 01b | ft_alpacacur.jsonl |
| Fine-tunes, 11 checkpoints each | 07 | safetypulse-lora-* |
| Monitoring: CIS, spot-check, ASR; thresholds on dev | 08 | safetypulse-logs/monitor |
| Repair strengths on dev; final test audit | 09, 10 | safetypulse-logs/repair, report, audits |
| SafeTune → AuditKIT before/after | 11 | kaggle/executed/11_* |
| Larger-GPU runs (CircuitKIT, 8B judge, CuratorKIT fine-tune) | tools/hf_jobs/ |
safetypulse-logs/hf_jobs |
Fixed choices: seed 0 for data splits; fine-tune seeds 0 (dev) and 1–3 (test); dev and test runs never overlap;
thresholds and repair strengths are chosen on dev only, then applied unchanged to test. Model and judge revisions
and dataset hashes are recorded in kaggle/executed/*/RUN_SUMMARY.md and safetypulse-eval/manifest.json.
Package tests, from a clone: pip install -e ".[dev,peft]" && pytest (186 tests, run in CI on Python 3.10–3.12).
Documentation
Report · Research plan · Architecture · Data · Evaluation protocol · Risk, ethics, licensing · Withheld artifacts · Kaggle and HF resources · Decisions
Responsible use
CIS is a screening signal: PASS does not certify a model as safe, and release decisions need a full audit. Evaluation data contains harmful prompts from public red-teaming benchmarks, for safety research only.
Licence
Code: Apache-2.0 (ADR-0004). Released adapters and data: CC-BY-NC-4.0,
inherited from Tiny Aya and Alpaca; benchmark data keeps its source licence. The Lexsi toolkits keep their own
licences and are used only through safetypulse.adapters.
Metadata
Release files for safetypulse 0.1.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| safetypulse-0.1.1.tar.gz | 92.6 kB | With provenance | Details for safetypulse-0.1.1.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| safetypulse-0.1.1-py3-none-any.whl | Python 3 | none | any | Details for safetypulse-0.1.1-py3-none-any.whl |
Total release size: 161.0 kB
Release files / safetypulse-0.1.1.tar.gz
| Download URL | safetypulse-0.1.1.tar.gz |
|---|---|
| Size | 92.6 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
29ccc688056a8f4b057334a2ffc969b5ba89bfd9aa3277bbed0d14d220cba6aa
|
|
BLAKE2b-256 checksum How to use checksums |
4c32ae1a8e0969d9c47466cff76ac4539578b97eaafd9909fd220387f9200002
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Nov 9, 2026.
Transparency logRelease files / safetypulse-0.1.1-py3-none-any.whl
| Download URL | safetypulse-0.1.1-py3-none-any.whl |
|---|---|
| Size | 68.4 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
2df8298c3ec960ef9a96c725e29b68e41f629cba02b8389ac6e62e03d9ecaba2
|
|
BLAKE2b-256 checksum How to use checksums |
ec8cf67fdc63a90c7963a15d65469cc71fe7d642f7df1c532f7e8dd746db8773
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Nov 9, 2026.
Transparency logSecurity for safetypulse 0.1.1
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
Both files in this release have been uploaded with provenance metadata.
Attestation for safetypulse-0.1.1.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Nov 9, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.