Metadata-Version: 2.4
Name: memaudit
Version: 0.2.0
Summary: Local Trainer/TRL plugin that audits fine-tunes for membership leakage and regurgitation: two verdicts, canaries, PEFT pre-flight, no phone-home
Author: memaudit contributors
License-Expression: Apache-2.0
Project-URL: Homepage, https://github.com/mem-audit/memaudit
Project-URL: Documentation, https://ansh200516.github.io/memaudit-site/
Project-URL: Source, https://github.com/mem-audit/memaudit
Project-URL: Issues, https://github.com/mem-audit/memaudit/issues
Keywords: memorization,membership-inference,canary,privacy,huggingface,trl,lora,peft,gdpr,edpb,fine-tuning
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: Intended Audience :: Science/Research
Classifier: Intended Audience :: Legal Industry
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
Classifier: Topic :: Security
Classifier: Typing :: Typed
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: transformers>=4.56.2
Requires-Dist: torch>=2.5
Requires-Dist: datasets>=2.20
Requires-Dist: numpy>=1.24
Requires-Dist: scipy>=1.10
Provides-Extra: peft
Requires-Dist: peft<0.21,>=0.15; extra == "peft"
Provides-Extra: trl
Requires-Dist: trl<1.0,>=0.15; extra == "trl"
Provides-Extra: hub
Requires-Dist: huggingface_hub>=0.24; extra == "hub"
Provides-Extra: dev
Requires-Dist: pytest>=8.0; extra == "dev"
Dynamic: license-file

# memaudit

[![PyPI](https://img.shields.io/pypi/v/memaudit)](https://pypi.org/project/memaudit/)
[![Python](https://img.shields.io/pypi/pyversions/memaudit)](https://pypi.org/project/memaudit/)
[![License](https://img.shields.io/pypi/l/memaudit)](https://github.com/mem-audit/memaudit/blob/main/LICENSE)

**Training-data memorization auditor for Hugging Face Trainer / TRL fine-tunes.**

A local, Apache-2.0 plugin that answers two questions every fine-tune in a regulated setting should document ([EDPB Opinion 28/2024](https://www.edpb.europa.eu/) para 55 / para 58):

1. **Membership** -- can an attacker with logprob access tell what was trained on?
2. **Regurgitation** -- does the model emit training content when prompted with a prefix?

memaudit injects pre-registered canaries into the *raw* dataset, runs a PEFT-aware pre-flight when training starts, and writes `memaudit-report.json` when training ends. The same engine is available post-hoc (`memaudit audit --ref auto`). **Entirely on your machine** -- no phone-home, no account, no SaaS.

> This tool produces **evidence of resistance to the attacks it actually runs**. It does **not** make you GDPR / AI Act / CNIL compliant.

## Install

```bash
pip install memaudit                 # core
pip install "memaudit[peft,trl]"     # LoRA + SFTTrainer lint (recommended for fine-tunes)
# from source:
git clone https://github.com/mem-audit/memaudit.git && cd memaudit
pip install -e ".[dev,peft,trl]"
```

Requires Python 3.10+ and `transformers>=4.56.2` (works on 5.x).

## 30-second start

```bash
pip install memaudit
memaudit demo
```

Positive-control demo on a tiny overfit model -- proves the instrument can detect memorization and keep controls clean. Checked-in report: `examples/demo-report.json`.

## Usage

```python
from memaudit import generate_canaries, inject, MemorizationAuditCallback

canaries = generate_canaries(
    tokenizer, n=32, n_controls=100, family="high_ppl",
    repetitions=(1, 4, 16), seed=0,
)
train_ds, manifest = inject(train_ds, canaries, fmt="auto", seed=0)

trainer.add_callback(
    MemorizationAuditCallback(
        trainer=trainer, manifest=manifest, real_sample=64, ref="auto",
    )
)
trainer.train()   # writes <output_dir>/memaudit-report.json
```

**Injection is a pre-train helper** -- transformers builds the dataloader before `on_train_begin`, and TRL tokenizes inside `SFTTrainer.__init__`. The secret goes on the **trainable** side (`completion` / assistant turn / `text` body); prompt-side placement is refused.

Post-hoc (or after ZeRO-3 / FSDP, where in-callback scoring is deferred):

```bash
memaudit audit --model ./out --canary-set ./out/memaudit-manifest.json \
               --dataset ./train.jsonl --ref auto
```

`ref="auto"` uses `peft.disable_adapter()` on an unmerged LoRA (one model copy). Full fine-tunes need `--ref <base-checkpoint>` or explicit `--ref none`.

## What you get

Every run writes schema `1.2.0` JSON with **both verdicts**, negative controls, Clopper-Pearson CIs, provenance hashes, a shipped EDPB `compliance_annex`, and an explicit limitations statement.

| Verdict | Headline field | Notes |
|---|---|---|
| Membership | `membership.tpr_at_1pct_fpr` | Base-calibrated Min-K%++ on the secret span; threshold from **held-out canary controls** (default 1% FPR). `null` when underpowered (`headline_valid=false` or `audit_profile=smoke`). |
| Regurgitation | `regurgitation.overall.rate` | Prefix-prompted completion (exact / BLEU>0.75 / NED<=0.1). Counts are protocol-scoped -- not "no extraction risk". |

Membership and regurgitation **routinely disagree**. Scores are on the **secret span only**; full-sequence loss collapses detection.

Also in the report: `membership.by_repetition` (1x / 4x / 16x / pooled), `membership.auc` (secondary), `canaries.requested_family` / `actual_generator`, `negative_controls`, optional `real_records.set_level` (needs `held_out=`), `compliance_annex`, `report_sha256` + sidecar, `phone_home: false`.

Pluggable membership scorers: [`docs/membership-scorers.md`](docs/membership-scorers.md).

## Measured results

### Flagship: TinyLlama + Alpaca LoRA (powered)

Scale: **TinyLlama-1.1B-Chat**, **20,000** `tatsu-lab/alpaca` rows, LoRA **r=8**, **1 epoch**, **100** inserted / **200** controls, **0.874%** canary budget. Measured 2026-08-30 (Apple M3 Pro, MPS). Full write-up: [`docs/case-study-alpaca.md`](docs/case-study-alpaca.md) | [live site](https://ansh200516.github.io/memaudit-site/).

| Run | Generator | TPR @ 1% FPR | 95% CI | AUC | Tiers (1x / 4x / 16x) | Regurg |
|---|---|---|---|---|---|---|
| **Headline** (`examples/alpaca-powered-report.json`) | requested `high_ppl` -> **`model_scored_high_ppl`** | **0.100** (10/100) | **[0.049, 0.176]** | **0.837** | **0/34 / 1/33 / 9/33** | **0/100** |
| Appendix uniform_vocab (`examples/alpaca-powered-report-v0.1-uniformvocab.json`) | requested `high_ppl` -> **`uniform_vocab`** fallback | **0.180** (18/100) | **[0.110, 0.269]** | **0.776** | **0/34 / 2/33 / 16/33** | **0/100** |

The pooled 10% headline is substantially a **duplication/exposure stress signal** (16x tier drives most detections), not a 10% probability for a single-exposure record. Regurgitation is under **this prefix/decoding/exact-match protocol** only.

Reproduce: `pip install "memaudit[peft,trl]" && python examples/alpaca_case_study.py`

### Other measured runs

| Run | Scale | TPR @ 1% FPR | Notes |
|---|---|---|---|
| `memaudit demo` | TinyDemoLM positive control | **1.000** (16/16) | Instrument validation; ~99% token budget on purpose. `examples/demo-report.json` |
| distilgpt2 + LoRA | Pretrained, honest <=1% budget | **0.000** at 1% FPR | n=16 and n=100 rows; risky config AUC **0.848** with multi-seed edge signal. [`benchmarks/README.md`](benchmarks/README.md) |
| TRL SFTTrainer live | distilgpt2 + LoRA, `completion_only_loss` | **0.000** (0/16) | Integration path; preflight survival scan 16/16. [`benchmarks/README.md`](benchmarks/README.md) |

A 12-canary Alpaca first look (TPR 0.500, wide CI) is archived in `examples/alpaca-case-study-report.json` -- not a headline. See the case-study appendix.

## Profiles & CLI

| Profile | Shape | TPR@FPR headline |
|---|---|---|
| `smoke` | cheap | refused |
| `routine` | 32 + 100 controls (default) | yes (if >=100 controls) |
| `powered` | 100 + 200 controls, reps `{1,4,16}` | yes |

```bash
memaudit report out/memaudit-report.json          # EDPB annex as markdown
memaudit verify out/memaudit-report.json          # integrity check (content hash)
memaudit doctor --output-dir examples             # env + demo + schema
memaudit audit ... --seeds 0,1,2                  # audit-procedure stability block
memaudit audit ... --release-context public-api   # para 46 release context
bash scripts/acceptance.sh                          # buyer acceptance gate
```

Public helper: `from memaudit import inject` (implementation: `memaudit.injection`).

## Scope

| In scope | Out of scope |
|---|---|
| Membership inference (canary MIA, TPR @ 1% FPR + CI) | Model inversion / reconstruction |
| Prefix-prompted regurgitation | Attribute inference, PII discovery |
| LoRA / PEFT pre-flight + `disable_adapter()` scoring | Shadow-model LiRA, DP certificates |
| Set-level signal on *your* records (with `held_out=`) | Broad red-teaming |

| Family | Construction |
|---|---|
| `high_ppl` **(default)** | Rejection-sample from base model into a PPL band; falls back to `uniform_vocab` if no model/corpus (recorded in `actual_generator`) |
| `unigram` / `bigram` | Rare tokens under corpus n-grams; uniform fallback if no corpus |
| `structured` | `CANARY-ID:...` template |
| `random` | Uniform vocab draws (control twins) |
| `new_token` | Gated -- frozen-embedding LoRA cannot train new rows; no vocab resize |

Default canaries use the **existing vocabulary** -- memaudit **never resizes the vocab**. Pre-flight **blocks** silent false confidence (wrong placement, masked labels, empty inclusion coins, <100 controls for headline TPR). TPR@1%FPR is **refused**, not fabricated, when underpowered.

## Architecture

```
generate_canaries() -> inject() -> MemorizationAuditCallback -> run_audit() / memaudit audit
```

Injection cannot live in the callback (dataloader + TRL tokenization happen first). ZeRO-3/FSDP defer in-callback scoring to the CLI. Ten implementation landmines (no callback injection, secret-span scoring, `processing_class`, two verdicts always) are encoded in source -- see module docstrings.

## Limitations

- Small canary counts -> wide CIs. v0.1 defaults are a CPU-friendly start, not a regulatory sample size.
- Thresholds calibrate on **this run's controls**; they do not transfer across model families.
- Real-record ranking without `held_out=` is descriptive only -- no FPR, not evidence about individuals.
- Black-box final-model audits are structurally loose. A small TPR is not a privacy certificate.
- Multi-seed mode measures **audit-procedure variance** (bootstrap calibration + real-record sampling), not re-training variance.
- DPO / GRPO / Hub model-card push out of scope for v0.1. LoRA-aware, not LoRA-only.

## Verified stack

| Piece | LoRA bench | Clean wheel |
|---|---|---|
| Python | 3.12.11 | 3.12.11 |
| torch | 2.7.1 | 2.13.0 |
| transformers | 4.56.2 | 5.16.1 |
| peft / trl | 0.20.0 / 0.29.1 | optional extras |

**Known-bad:** transformers 5.16.x + torch 2.6.dev hangs on FSDP imports. Recommended LoRA pin: `transformers==4.56.2`, `torch>=2.5,<2.8`, `peft==0.20.0`. Do not use `--system-site-packages` over a conda torch nightly.

## Links

| | |
|---|---|
| PyPI | [pypi.org/project/memaudit](https://pypi.org/project/memaudit/) |
| Case study | [`docs/case-study-alpaca.md`](docs/case-study-alpaca.md) |
| Benchmarks | [`benchmarks/README.md`](benchmarks/README.md) |
| Scorer plug-in | [`docs/membership-scorers.md`](docs/membership-scorers.md) |
| Site | [ansh200516.github.io/memaudit-site](https://ansh200516.github.io/memaudit-site/) |
| Hugging Face | [huggingface.co/memaudit](https://huggingface.co/memaudit) |
| License | Apache-2.0 |
