Personalize this lesson
Adapt explanations and teaching visuals to your background and preferred voice.
An enterprise customer support copilot is in its final pre-launch security review. A red-team analyst enters a snippet from an internal troubleshooting ticket: "Incident 4092: Customer reported recurring billing error on...". The model immediately completes the sentence with an unmasked credit card tail, a personal home address, and an internal note recording that the customer entered psychiatric medical leave.
The engineering team is stunned. They ran regex scrubbers across the training corpus before fine-tuning. Names were stripped, and user identifiers were replaced with random hashes. Yet the model still reproduced the customer's private history verbatim.
Scrubbing explicit names leaves unique phrase combinations, rare tokens, and semantic correlations completely intact. Deep language models have billions of parameters; when an unusual sequence appears in training data, gradient descent fits it tightly. Redaction can't change that fundamental memorization dynamic.
Differential privacy frames this problem as a mathematical neighboring-dataset contract: if every single record contributed by one protected person vanished before training began, how much could the probability distribution of released model outputs change?[1][2]
Answering that question requires choosing the exact unit you promise to protect, bounding each unit's mathematical influence, injecting calibrated randomness, and tracking cumulative privacy loss across every released checkpoint.
We'll work within the central privacy model: a trusted infrastructure service accesses raw records to run training, but the released model weights and generated text must satisfy provable privacy bounds. Differential privacy doesn't replace database access controls or scrub internal logs. It establishes a formal limit on what an external observer can extract from the final system.
Why redaction fails against modern extraction attacks
Redaction treats privacy as a syntactic text-matching task. If a word matches an entity dictionary or regex pattern, you mask it. Real privacy attacks operate on semantic statistics and model capacity.
Modern privacy evaluation focuses on three primary attack vectors:
- Training data memorization and extraction: Overparameterized neural networks easily memorize rare or unique training sequences. Attackers probe models with targeted prompt prefixes, greedy decoding, or beam search to extract memorized strings verbatim. Security teams evaluate this vulnerability using canary insertion: inserting artificial secrets with controlled repetition counts into the training data, then measuring how many exposures allow an attacker to reconstruct the secret. Without mathematical protection, models can memorize sequences seen only once or twice.
- Membership inference attacks (MIA): An adversary tests whether a known document belonged to the training set [3]. Because standard training minimizes empirical loss on training examples, the model exhibits systematically lower loss and lower perplexity on members than on non-members. Attackers calibrate this signal by comparing the target model's loss to a public reference model's loss () or zlib compression entropy. If the target model finds a sentence significantly more probable than the reference model does, it flags that record as training data.
- Attribute reconstruction: An attacker combines partial knowledge of a user's record with query access to the trained model to infer omitted sensitive fields, such as diagnostic codes or salary brackets.
| Defense strategy | What it actually establishes | Vulnerability left open |
|---|---|---|
| Regex name scrubbing | Removes specific known string tokens | Syntactic masking misses unique context, rare phrases, and semantic fingerprints |
| Identifier hashing | Converts persistent IDs into fixed pseudonyms | Pseudonyms remain linkable across records; correlations still leak |
| Passing membership test | Confirms one tested attack failed on selected samples | Gives an empirical lower bound; offers zero guarantee against future attacks |
| Differentially private training | Guarantees bounded output distribution divergence under any neighboring change | Trades a quantified drop in training efficiency or raw utility for provable safety |
A failed empirical attack doesn't prove that your model is secure. It only proves that the specific auditor, threshold, and feature set you tested couldn't separate members from non-members. Differential privacy provides a mathematical upper bound across all possible attacks, including adversaries with arbitrary background knowledge.
Why does a passing membership inference test fail to guarantee that a customer's support ticket can't be extracted?
Answer
An attack test only establishes an empirical lower bound on privacy leakage for one specific adversary, interface, and test split. It doesn't bound what a stronger adversary with better auxiliary data or different decoding strategies can extract. Differential privacy provides a worst-case mathematical bound over all possible adversaries.
Start with the unit you promise to protect
Before configuring an epsilon or tuning noise scales, write one concrete contract: "one protected unit is ___." Two datasets are neighbors when they differ by that single protected unit.
In enterprise support data, that unit might be one row, one conversation ticket, or one customer account.
| Protected unit | Neighboring dataset definition | Operational failure mode |
|---|---|---|
| Row | Remove one training message | One customer sent fifty messages across several tickets |
| Conversation | Remove one multi-turn conversation | A user participated in multiple support incidents over time |
| User | Remove every message and ticket from that user | Missing identity tracking prevents bounding total user contributions |
Suppose Alice submitted twelve support messages across three tickets. A row-level guarantee bounds what happens when one message is removed. A user-level guarantee bounds what happens when all twelve messages disappear simultaneously. That's the exact same corpus under two completely different neighboring relations, yielding two radically different privacy promises.
The definition also depends on the adjacency convention:
- Add/remove adjacency: Neighbor contains one fewer (or one additional) protected unit than , so . Dataset size changes by one.
- Replace-one adjacency: Neighbor replaces one protected unit's complete value with another arbitrary valid value, keeping total population size fixed, so .
Replacing a vector bounded in can swing an aggregate sum from to , spanning a distance of . Removing that vector only changes the sum by . Replacing doubles the sensitivity bound compared to removal. You can't swap conventions without updating your sensitivity calculations.
Turn this into Cynthia Dwork's formal condition. For every possible output event , a randomized mechanism satisfies -differential privacy if:[1][2]
The probabilities reflect the internal randomness of mechanism , not randomness in picking the training dataset. The condition must hold in both directions ( to and to ) for every neighboring pair and every measurable output event .
Parameter (epsilon) is the privacy budget. It sets a strict multiplicative ceiling on the log likelihood ratio an observer can gain about any individual's presence. When , , meaning the odds of any model output can shift by at most . When , , leaving a wide window for distinguishing members.
Parameter (delta) provides additive slack for unlikely tail events where the strict ratio fails. In machine learning, isn't a minor failure percentage. Setting is fatal: a mechanism could publish one raw customer record with probability and still satisfy the inequality mathematically! For a corpus of training records, must be strictly smaller than , typically or . Setting gives pure differential privacy.

Consider a toy mechanism you can evaluate by hand. A support customer answers a sensitive yes/no question truthfully with probability and reports the opposite answer with probability :
| True private response | Probability reporting Yes | Probability reporting No |
|---|---|---|
| Yes | 0.75 | 0.25 |
| No | 0.25 | 0.75 |
The largest possible probability ratio for either output is . This randomized-response mechanism satisfies pure differential privacy with and . It's a local differential privacy mechanism because noise is added on the client device before anyone else observes the response.[2]
Training an LLM uses the central model instead. Gradients from thousands of examples are pooled, clipped, and perturbed inside a secure compute boundary before weights leave the cluster.
A machine learning engineer trains an LLM on 50,000 patient records and reports a privacy guarantee with epsilon = 2.0 and delta = 0.05. Why is this delta value unacceptable?
Answer
Delta represents the probability that the differential privacy bound fails completely. A delta of 0.05 means the mechanism could leak raw unperturbed records for 5% of the dataset (2,500 patients) while technically satisfying the mathematical inequality. In machine learning, delta must be strictly smaller than 1/N, typically 10^-5 or 10^-6 for this corpus size.
Mechanisms and sensitivity scaling
Differential privacy adds noise proportional to the query's global sensitivity. For any function mapping a dataset to a vector space, sensitivity measures the maximum change between any two neighboring datasets and :
Two foundational mechanisms inject this noise:
The Laplace mechanism adds independent noise drawn from to each coordinate. It guarantees pure -differential privacy. For scalar counts or low-dimensional database queries, Laplace noise is simple and effective.
In deep neural networks, the Laplace mechanism fails completely. In a model with parameters, the expected norm of independent Laplace noise scales linearly with . When updating a 100-million-parameter model, adding coordinate-wise Laplace noise injects so much variance that optimization collapses immediately.
The Gaussian mechanism adds noise drawn from , where standard deviation satisfies:
The Gaussian mechanism delivers -differential privacy. Its required noise scales with sensitivity () rather than sensitivity. Because the norm of a vector is bounded by its maximum coordinate times rather than , the Gaussian mechanism injects vastly less total variance into high-dimensional gradient vectors. That makes the Gaussian mechanism the standard engine for private deep learning.
Differentially private stochastic gradient descent
Standard stochastic gradient descent computes the loss gradient over a mini-batch, averages those vectors, and steps the parameters. That average is directly vulnerable to outliers: if Alice's private record has an unusually large gradient with norm while normal samples have norm , Alice's record pulls the aggregate step toward her exact tokens.
Differentially private stochastic gradient descent (DP-SGD) fixes this vulnerability by bounding each contribution individually before summation [4].
The DP-SGD algorithm proceeds in five explicit phases on each training step :
- Poisson subsampling: Form batch by including each training record independently with sampling probability , where is the target batch size and is total dataset size.
- Per-sample backpropagation: For each example , compute its individual parameter gradient separately before any batch reduction occurs.
- Per-sample clipping: Enforce an norm threshold on each vector: If , the gradient is untouched. If , the gradient keeps its exact direction but its length shrinks to .
- Summation and Gaussian noise injection: Sum the clipped vectors and add calibrated Gaussian noise scaled to sensitivity : Adding or removing one sample changes the sum by at most , so the sensitivity of the summation is .
- Descent step: Divide the noisy aggregate by target batch size and update parameters:

A frequent bug in custom implementations is clipping the batch average after aggregation: clip(mean(gradients), C). This bounds the final update vector's length to , but neighboring dataset averages can still point in opposite directions, creating a worst-case difference of between outputs. Clipping must occur per sample before aggregation to establish sensitivity for the batch sum.
To verify this geometry, test per-sample vector clipping on concrete gradient vectors. Outliers project onto the unit ball while in-bounds gradients remain untouched:
1import math
2
3def clip_vector_l2(vector: list[float], bound: float) -> list[float]:
4 if bound <= 0:
5 raise ValueError("bound must be positive")
6 norm = math.hypot(*vector)
7 if norm == 0.0 or norm <= bound:
8 return [round(x, 4) for x in vector]
9 scale = bound / norm
10 return [round(x * scale, 4) for x in vector]
11
12sample_gradients = [
13 [0.4, 0.3], # norm = 0.5 <= 1.0 (unclipped)
14 [3.0, 4.0], # norm = 5.0 > 1.0 (scaled by 1.0 / 5.0 = 0.2)
15 [0.6, 0.8], # norm = 1.0 == 1.0 (unclipped)
16]
17clip_bound = 1.0
18clipped = [clip_vector_l2(g, clip_bound) for g in sample_gradients]
19
20print(f"sample gradients: {sample_gradients}")
21print(f"clipped gradients: {clipped}")
22
23sum_g = [sum(dim) for dim in zip(*clipped)]
24print(f"clipped sum before noise: [{sum_g[0]:.2f}, {sum_g[1]:.2f}]")1sample gradients: [[0.4, 0.3], [3.0, 4.0], [0.6, 0.8]]
2clipped gradients: [[0.4, 0.3], [0.6, 0.8], [0.6, 0.8]]
3clipped sum before noise: [1.60, 1.90]The outlier [3.0, 4.0] with length scales down to [0.6, 0.8], whose length is . The gradient's direction is preserved, but its ability to pull the model parameters toward its private values is clamped.
Now consider what happens when Alice contributes multiple messages. If Alice wrote two tickets with gradients and , example-level clipping bounds each message to , leaving Alice with a combined influence of . A row-level clip doesn't bound Alice's full user-level influence.
User-level fine-tuning groups each user's records, sums their gradients, and clips that combined user vector [5]. Grouping records per user binds total customer influence before aggregation. Here we test Alice's two tickets against Bao's single ticket using a fixed public normalizer of two:
1def clip_scalar(gradient: float, bound: float) -> float:
2 if bound <= 0:
3 raise ValueError("bound must be positive")
4 return max(-bound, min(gradient, bound))
5
6user_gradients = {"Alice": [0.5, 5.0], "Bao": [0.25]}
7clip_bound = 1.0
8
9for user, gradients in user_gradients.items():
10 row_clipped_sum = sum(clip_scalar(value, clip_bound) for value in gradients)
11 user_clipped = clip_scalar(sum(gradients), clip_bound)
12 print(f"{user}: row-clipped sum={row_clipped_sum:.2f}, user-clipped={user_clipped:.2f}")
13
14normalizer = 2 # Public and unchanged across neighboring datasets.
15user_contributions = {
16 user: clip_scalar(sum(values), clip_bound)
17 for user, values in user_gradients.items()
18}
19with_alice = sum(user_contributions.values()) / normalizer
20without_alice = user_contributions["Bao"] / normalizer
21print(f"with Alice, before noise: {with_alice:.3f}")
22print(f"without Alice, before noise: {without_alice:.3f}")
23print(f"change from removing Alice: {with_alice - without_alice:.3f}")
24assert with_alice - without_alice <= clip_bound / normalizer1Alice: row-clipped sum=1.50, user-clipped=1.00
2Bao: row-clipped sum=0.25, user-clipped=0.25
3with Alice, before noise: 0.625
4without Alice, before noise: 0.125
5change from removing Alice: 0.500Removing Alice changes the normalized sum by exactly , which equals . User-level grouping enforces that Alice's entire presence can't shift the pre-noise aggregate by more than .
Why must DP-SGD compute gradients per sample before batch reduction, rather than clipping the batch average gradient?
Answer
Clipping the batch average bounds only the length of the final step, but neighboring datasets could still produce batch averages that point in opposing directions, creating an unbounded difference. Per-sample clipping bounds each individual contribution to norm C, guaranteeing that adding or dropping one sample shifts the batch sum by at most C.
Account for every exposed release
Clipping bounds sensitivity for one training step. Training an LLM takes thousands of steps. A privacy accountant tracks how privacy loss accumulates over the full optimization run [6].
If you apply basic composition, privacy budgets add linearly: running steps with step budget yields total . A training run of steps with would report , an astronomically useless number.
Modern DP-SGD implementations rely on the Moments Accountant or Rényi Differential Privacy (RDP) [4][7]. RDP measures the Rényi divergence of order between neighboring output distributions:
For a Gaussian mechanism with Poisson subsampling probability , Rényi divergences compose linearly across steps:
Converting RDP back to -DP at the end of training yields an asymptotic bound:
Notice the factor . This is subsampling amplification: because an attacker only sees an individual's data if that individual was included in the Poisson batch, privacy protection is amplified by . In large datasets where or , subsampling amplification reduces cumulative epsilon by orders of magnitude compared to naive composition.
You can evaluate this directly with Opacus's RDPAccountant. We compare cumulative epsilon across 300 and 600 training steps under fixed sampling rate and noise multiplier :
1from opacus.accountants import RDPAccountant
2
3for steps in [300, 600]:
4 accountant = RDPAccountant()
5 for _ in range(steps):
6 accountant.step(noise_multiplier=1.0, sample_rate=0.01)
7 epsilon = accountant.get_epsilon(delta=1e-5)
8 print(f"steps={steps}, epsilon={epsilon:.3f}, delta=1e-5")1steps=300, epsilon=1.451, delta=1e-5
2steps=600, epsilon=1.748, delta=1e-5Doubling training steps from 300 to 600 increases epsilon from to , rather than doubling it to . Sublinear scaling allows practical models to train for thousands of steps while maintaining strict privacy budgets.
Once training finishes, what about serving customer inference requests? Under the post-processing theorem, applying any arbitrary function to the output of an -differentially private algorithm without accessing private data costs zero additional privacy budget [2]. Serving ten million inference queries from the released model doesn't spend any extra training epsilon.

Post-processing applies only to operations that touch public data and the released weights. If you compute validation scores on private held-out records, run hyperparameter grid searches on private logs, or save unnoised intermediate checkpoints, those operations expose private data outside the accounted transcript and spend separate privacy budget.
Release policies track this cumulative exposure across candidate models using release ledgers:
1from decimal import Decimal
2
3released = [
4 (Decimal("1.2"), Decimal("0.000001")),
5 (Decimal("1.1"), Decimal("0.000001")),
6]
7candidate = (Decimal("0.9"), Decimal("0.000001"))
8proposed_epsilon = sum(eps for eps, _ in released) + candidate[0]
9proposed_delta = sum(d for _, d in released) + candidate[1]
10allowed = (proposed_epsilon <= Decimal("3.0")
11 and proposed_delta <= Decimal("0.000003"))
12
13print(f"candidate total: epsilon={proposed_epsilon:.1f}, delta={proposed_delta:.6f}")
14print(f"release allowed: {allowed}")
15assert not allowed1candidate total: epsilon=3.2, delta=0.000003
2release allowed: FalseThe release ledger blocks the candidate because total epsilon () exceeds the release cap (). Using Decimal prevents floating-point rounding errors from approving unauthorized model deployments.
The privacy-utility trade-off in modern LLMs
Applying DP-SGD to a multi-billion-parameter language model exposes a brutal geometric obstacle: the curse of dimensionality.
When you add Gaussian noise to a model with parameters, the expected Euclidean norm of the injected noise vector is:
In a 7-billion-parameter model, . If you set clipping threshold and noise multiplier , each update step injects a random noise vector with norm exceeding into the weights!
True gradient sums from a standard mini-batch of 64 or 128 examples have norm on the order of to . The random noise vector dwarfs the learning signal by a factor of nearly a thousand. Full-parameter DP-SGD on large models fails to converge unless you scale batch sizes into the tens of thousands (), which requires immense compute cluster memory.
Parameter-efficient fine-tuning with DP-LoRA eliminates this dimensionality trap [8][5].
Instead of updating all weights , Low-Rank Adaptation freezes the pre-trained base model completely and injects trainable low-rank decomposition matrices:
Setting rank reduces the number of trainable parameters from billions down to a few million (often of total parameters).
In DP-LoRA, gradients are computed, clipped, and perturbed only for matrices and . The dimension drops from to . Because noise norm scales with , the total injected noise norm drops by a factor of . The gradient signal-to-noise ratio jumps dramatically, allowing DP-LoRA to learn domain tasks with high fidelity under strict privacy budgets.

This architecture creates an important pre-training privacy asymmetry:
- Pre-training corpus exposure: The foundation model was trained on trillions of public and crawled web tokens without differential privacy. It may already have memorized text from those public sources. DP fine-tuning doesn't purge or scrub pre-existing foundation model memorization.
- Fine-tuning corpus protection: The fine-tuning phase uses DP-SGD on proprietary domain data (such as medical records or customer support logs). The resulting adapter weights guarantee that an attacker querying the fine-tuned model can't extract or reconstruct private records from that fine-tuning dataset beyond the accounted bound.
By anchoring on frozen pre-trained representations and training only low-rank adapters with DP-SGD, teams ship enterprise copilots that master complex domain tasks while providing provable privacy guarantees.