# LLM Memorization Landscape

> Understand and mitigate unintended memorization of training data in LLMs by systematizing mechanisms, detection methods, and mitigation strategies across the model lifecycle.

- Skill: `adu2021/llm-memorization-landscape` (Agent Skill)
- Install (CLI): `npx skillmds@latest add adu2021/llm-memorization-landscape`
- Raw SKILL.md: https://api.skillmd.com/api/skills/adu2021/llm-memorization-landscape/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: AI & ML
- License: MIT
- Author: adu2021 (https://skillmd.com/u/adu2021)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/adu2021/llm-memorization-landscape

---


# LLM Memorization Landscape: Systematizing Risks and Defenses

Large language models inadvertently memorize and reproduce training data, raising privacy, copyright, and legal concerns. Yet memorization is complex—models may remember facts (beneficial) while leaking sensitive information (harmful). Traditional approaches treat memorization as a binary problem, but it emerges differently during pre-training, fine-tuning, RLHF, and distillation. Practitioners need clarity on what memorization means, how to detect it, and how to mitigate it.

This systematization of knowledge unifies research across 200+ papers, providing practitioners with frameworks to understand memorization as an emergent property of compression and data repetition, detect risks through multiple methodologies, and choose mitigation strategies matching their requirements and constraints.

## Core Concept

Memorization is not a single phenomenon but an emergent property of how neural networks compress data. Different memorization types exist: verbatim memorization (exact training data reproduction), approximate memorization (semantic similarity), and extractable memorization (information derivable through model queries). Critically, the same model exhibits different memorization levels during different training stages—pre-training with diverse data shows less extraction risk than fine-tuning on smaller specific datasets.

The framework distinguishes beneficial memorization (knowing that Paris is the capital of France) from harmful leakage (reproducing a specific training email containing a private key). Effective mitigation requires identifying which type occurs and when, not eliminating memorization entirely (which would require infeasible retraining).

## Architecture Overview

- **Memorization Type Taxonomy**: 8+ definitions (verbatim, approximate, eidetic, extractable, discoverable, k-extractable, probabilistic, counterfactual) and their relationships
- **Risk Lifecycle Mapping**: How memorization emerges across pre-training → fine-tuning → RLHF → distillation stages
- **Detection Methodology Stack**: Extraction attacks (prefix attacks, divergence attacks), membership inference attacks (MIAs), soft prompting approaches
- **Mitigation Strategy Framework**: Training-time (DP-SGD, deduplication), post-training (unlearning, ParaPO), inference-time (MemFree decoding, activation steering)
- **Practical Evaluation Tools**: Benchmarks for measuring memorization, assessing mitigation trade-offs
- **Legal and Ethical Context**: Copyright implications, PII leakage risks, regulatory compliance

## Implementation

The following implements core components for detecting and mitigating memorization in LLMs.

**Step 1: Memorization Detection - Extraction Attacks**

This implements methods to identify when models have memorized training data.

```python
import torch
import torch.nn.functional as F
from typing import List, Tuple

class ExtractionAttack:
    """Detect memorization through extraction attacks."""

    def __init__(self, model, tokenizer):
        self.model = model
        self.tokenizer = tokenizer

    def prefix_attack(
        self,
        prefix: str,
        max_length: int = 100,
        num_samples: int = 5,
        temperature: float = 1.0
    ) -> List[str]:
        """
        Prefix attack: given partial training data, try to extract full sequence.
        High-quality completions suggest memorization.
        """
        self.model.eval()
        completions = []

        with torch.no_grad():
            for _ in range(num_samples):
                input_ids = self.tokenizer.encode(prefix, return_tensors="pt")

                # Generate completion
                output = self.model.generate(
                    input_ids,
                    max_length=max_length,
                    temperature=temperature,
                    top_p=0.9,
                    do_sample=True
                )

                completion = self.tokenizer.decode(output[0], skip_special_tokens=True)
                completions.append(completion)

        return completions

    def divergence_attack(
        self,
        train_sample: str,
        num_queries: int = 20,
        perturbation_size: int = 1
    ) -> float:
        """
        Divergence attack: measure model's ability to complete specific training sequences.
        Higher accuracy on train sequences than random sequences indicates memorization.
        """
        self.model.eval()
        train_perplexity = 0.0

        with torch.no_grad():
            for _ in range(num_queries):
                # Tokenize
                input_ids = self.tokenizer.encode(
                    train_sample, return_tensors="pt", truncation=True
                )

                # Forward pass
                outputs = self.model(input_ids, labels=input_ids)
                loss = outputs.loss

                train_perplexity += loss.item()

        avg_train_perplexity = train_perplexity / num_queries

        # Compare to random baseline (higher = more memorized)
        return avg_train_perplexity

    def measure_extractability(
        self,
        train_samples: List[str],
        random_samples: List[str]
    ) -> Tuple[float, float]:
        """
        Measure extractability: accuracy on train vs random sequences.
        Gap indicates degree of memorization.
        """
        train_perplexities = [self.divergence_attack(s) for s in train_samples]
        random_perplexities = [self.divergence_attack(s) for s in random_samples]

        avg_train = sum(train_perplexities) / len(train_perplexities)
        avg_random = sum(random_perplexities) / len(random_perplexities)

        # Extractability: ratio of train to random (>1 = memorized)
        extractability = avg_train / (avg_random + 1e-6)
        return avg_train, extractability
```

**Step 2: Membership Inference Attacks (MIAs)**

This detects if specific training examples were in the training set.

```python
class MembershipInferenceAttack:
    """Detect memorization through membership inference."""

    def __init__(self, model, tokenizer):
        self.model = model
        self.tokenizer = tokenizer

    def compute_perplexity(self, text: str) -> float:
        """Compute perplexity (lower = better prediction = likely memorized)."""
        self.model.eval()
        input_ids = self.tokenizer.encode(text, return_tensors="pt")

        with torch.no_grad():
            outputs = self.model(input_ids, labels=input_ids)
            loss = outputs.loss.item()

        # Perplexity = exp(loss)
        return min(torch.exp(torch.tensor(loss)).item(), 1e8)

    def shadow_model_attack(
        self,
        candidate_sample: str,
        shadow_models: List,
        threshold: float = None
    ) -> Tuple[float, bool]:
        """
        Shadow model attack: if perplexity on candidate is anomalously low,
        suggest it was in training set.
        """
        # Compute perplexity on target model
        target_ppl = self.compute_perplexity(candidate_sample)

        # Compute perplexity on shadow models (trained differently)
        shadow_ppls = [m.compute_perplexity(candidate_sample) for m in shadow_models]
        avg_shadow_ppl = sum(shadow_ppls) / len(shadow_ppls)

        # If target significantly lower, sample likely memorized
        if threshold is None:
            threshold = np.mean(shadow_ppls) - np.std(shadow_ppls)

        is_member = target_ppl < threshold
        confidence = 1.0 - (target_ppl / avg_shadow_ppl)

        return confidence, is_member

    def batch_membership_test(
        self,
        candidate_samples: List[str],
        shadow_models: List
    ) -> dict:
        """Test batch of samples for membership."""
        results = {
            "members": [],
            "non_members": [],
            "confidence_scores": []
        }

        for sample in candidate_samples:
            confidence, is_member = self.shadow_model_attack(sample, shadow_models)
            results["confidence_scores"].append(confidence)

            if is_member:
                results["members"].append(sample)
            else:
                results["non_members"].append(sample)

        return results
```

**Step 3: Memorization Mitigation - Differential Privacy**

This applies differential privacy to reduce memorization during training.

```python
class DifferentialPrivacyTraining:
    """Mitigate memorization through differential privacy during training."""

    def __init__(self, model, optimizer, noise_multiplier: float = 1.0, max_grad_norm: float = 1.0):
        self.model = model
        self.optimizer = optimizer
        self.noise_multiplier = noise_multiplier
        self.max_grad_norm = max_grad_norm

    def add_dp_noise(self, gradients: List[torch.Tensor]) -> List[torch.Tensor]:
        """Add Gaussian noise for differential privacy."""
        noisy_grads = []

        for grad in gradients:
            if grad is not None:
                # Clip gradient norm
                norm = torch.norm(grad)
                if norm > self.max_grad_norm:
                    grad = grad / (norm + 1e-6) * self.max_grad_norm

                # Add Gaussian noise scaled by noise_multiplier
                noise = torch.randn_like(grad) * self.noise_multiplier
                noisy_grad = grad + noise
                noisy_grads.append(noisy_grad)
            else:
                noisy_grads.append(None)

        return noisy_grads

    def train_step_with_dp(
        self,
        batch: dict,
        batch_size: int,
        dataset_size: int
    ) -> float:
        """Single training step with differential privacy."""
        # Forward pass
        outputs = self.model(**batch)
        loss = outputs.loss

        # Backward pass
        self.optimizer.zero_grad()
        loss.backward()

        # Clip gradients per-sample
        gradients = [p.grad for p in self.model.parameters()]
        noisy_gradients = self.add_dp_noise(gradients)

        # Apply noisy gradients
        with torch.no_grad():
            for param, noisy_grad in zip(self.model.parameters(), noisy_gradients):
                if noisy_grad is not None:
                    param.grad = noisy_grad

        # Update
        self.optimizer.step()

        # Compute privacy budget (epsilon, delta)
        epsilon = 1.0 / (self.noise_multiplier * dataset_size / batch_size)

        return loss.item(), epsilon
```

**Step 4: Memorization Mitigation - Unlearning**

This removes learned information about specific training examples.

```python
class MachineUnlearning:
    """Mitigate memorization by unlearning specific samples."""

    def __init__(self, model, learning_rate: float = 1e-5):
        self.model = model
        self.optimizer = torch.optim.Adam(model.parameters(), lr=learning_rate)

    def unlearn_sample(
        self,
        sample_to_unlearn: str,
        tokenizer,
        num_steps: int = 10,
        loss_weight: float = 1.0
    ) -> float:
        """
        Unlearn by gradient ascent on forgetting sample.
        Increases loss on specific examples to reduce memorization.
        """
        self.model.train()
        total_loss = 0

        for step in range(num_steps):
            # Tokenize
            input_ids = tokenizer.encode(sample_to_unlearn, return_tensors="pt")

            # Forward pass
            outputs = self.model(input_ids, labels=input_ids)
            loss = outputs.loss

            # Gradient ascent (maximize loss on forget sample)
            loss_to_maximize = loss * loss_weight

            self.optimizer.zero_grad()
            loss_to_maximize.backward()

            # Ascent step (negative gradient)
            with torch.no_grad():
                for param in self.model.parameters():
                    if param.grad is not None:
                        param.grad = -param.grad

            self.optimizer.step()
            total_loss += loss.item()

        return total_loss / num_steps

    def unlearn_batch(self, samples: List[str], tokenizer):
        """Unlearn multiple samples."""
        for sample in samples:
            self.unlearn_sample(sample, tokenizer)

    def verify_unlearning(
        self,
        unlearned_sample: str,
        control_sample: str,
        tokenizer
    ) -> Tuple[float, float]:
        """Verify that unlearning succeeded."""
        self.model.eval()

        with torch.no_grad():
            # Perplexity on unlearned sample (should increase)
            unlearned_ids = tokenizer.encode(unlearned_sample, return_tensors="pt")
            unlearned_loss = self.model(unlearned_ids, labels=unlearned_ids).loss.item()

            # Perplexity on control sample (should stay similar)
            control_ids = tokenizer.encode(control_sample, return_tensors="pt")
            control_loss = self.model(control_ids, labels=control_ids).loss.item()

        return unlearned_loss, control_loss
```

## Practical Guidance

**Hyperparameters and Configuration**

| Parameter | Recommended Value | Range | Notes |
|-----------|------------------|-------|-------|
| Noise Multiplier (DP) | 1.0-2.0 | 0.1-10.0 | Higher = more privacy; lower = better utility |
| Max Gradient Norm | 1.0 | 0.1-10.0 | Clips per-sample gradients before noise |
| Privacy Budget (ε) | 1.0-8.0 | 0.5-100 | Lower = stronger privacy; requires more data |
| Unlearning Steps | 10-50 | 1-100 | More steps = stronger unlearning but risk divergence |
| Unlearning Loss Weight | 1.0 | 0.1-10.0 | Controls strength of forgetting on sample |
| MIA Threshold (perplexity) | Mean - 1std | Adaptive | Calibrate on known train/test split |

**When to Use**

- Processing sensitive personal data (emails, medical records, financial info)
- Training on datasets with potential copyright-protected material
- Meeting regulatory requirements (GDPR, CCPA right to be forgotten)
- Reducing extraction attack risks in deployment
- Research on memorization properties of specific models
- Systems where user privacy is paramount

**When NOT to Use**

- Models where memorizing facts (e.g., historical dates) is essential
- Real-time inference requiring full model capability (mitigation adds overhead)
- Research where understanding what models learn takes priority over privacy
- Systems where utility/accuracy trade-offs are unacceptable
- Scenarios where differential privacy overhead is computationally infeasible

**Common Pitfalls**

- **Confusing memorization types**: Knowing facts ≠ memorizing PII. Distinguish beneficial from harmful memorization before applying blunt mitigation.
- **Over-relying on single detection method**: Extraction attacks, MIAs, and soft prompting measure different aspects. Use multiple methods for comprehensive assessment.
- **Ignoring utility costs**: Strong privacy (low ε) significantly degrades model performance. Balance privacy-utility requirements before deployment.
- **Assuming unlearning is permanent**: Unlearned information can re-emerge through continued training or distillation. Monitor over time.
- **Neglecting evaluation on edge cases**: Memorization detection works better on common sequences. Test on rare, sensitive examples.

## Reference

The Landscape of Memorization in LLMs: Mechanisms, Measurement, and Mitigation. https://arxiv.org/abs/2507.05578

