DP-SGD end-to-end¶
This guide covers the DP-SGD pipeline: calibrate a noise multiplier, clip per-example gradients, add Gaussian noise, update parameters, and checkpoint state.
For the conceptual deep-dives on each component, see the topic pages under User Guide: clipping, noise, sampling, accounting, optimizers. For the full DP-FTRL counterpart, see DP-FTRL end-to-end.
Why DP-SGD¶
DP-SGD adds independent Gaussian noise to a clipped gradient at every
training step. The privacy accountant composes per-step costs into a
total budget: each call to the noise mechanism is a separate
DpProcess, multiplied by the number of training steps.
Compared with DP-FTRL, DP-SGD needs no fixed training horizon or matrix strategy; its independent noise can be less useful on cumulative updates.
1. Calibration¶
Calibrate the noise multiplier to your privacy budget before training:
import opake.accounting as acc # cross-cutting (compose, calibrate)
import opake.dpsgd.accounting as dpsgd_acc # DP-SGD factories
dataset_size = 50_000
batch_size = 256
sample_rate = batch_size / dataset_size
num_steps = 1000
result = acc.calibrate(
acc.epsilon_budget(3.0, delta=1e-5),
lambda nm: dpsgd_acc.poisson(dpsgd_acc.gaussian(nm), sample_rate) * num_steps,
param_min=0.1, param_max=5.0,
)
noise_multiplier = result.param
dpsgd_acc.gaussian(nm) is the per-step Gaussian mechanism;
dpsgd_acc.poisson(..., sample_rate) wraps it with Poisson
amplification; * num_steps composes across the run. The resulting
DpProcess is what acc.calibrate solves for the noise multiplier.
2. Clipping¶
opake.dpsgd.clipping.clipped_grad wraps a per-example loss
function in vmap(grad(...)) semantics, clips each per-example
gradient to a fixed norm, and sums:
from opake.dpsgd.clipping import clipped_grad
def loss_fn(params, batch):
# ... per-example loss (NO mean over batch) ...
return loss
grad_fn, clip_state = clipped_grad(
loss_fn,
clipping_norm=1.0,
argnums=0,
batch_argnums=1,
normalize_by=batch_size,
)
For automatic threshold tuning across steps:
opake.dpsgd.clipping.adaptive_clipped_grad
(Andrew et al., 2021).
For AUTO-S smooth scaling: opake.dpsgd.clipping.auto_clipped_grad
(Bu et al., 2023).
3. Noise¶
opake.dpsgd.noise.gaussian_noise adds calibrated Gaussian noise to
the clipped gradient sum:
from opake.dpsgd.noise import gaussian_noise
from opake.random import key, split
key_sampling, key_noise = split(key(42), num=2)
noise_fn, noise_state = gaussian_noise(
noise_multiplier=noise_multiplier,
key=key_noise,
)
4. Sampling¶
DP-SGD pairs with Poisson subsampling:
from opake.dpsgd.sampling import PoissonSampler
sampler = PoissonSampler(
dataset, sample_rate=sample_rate, n_steps=num_steps, key=key_sampling,
)
PoissonSampler accepts truncated_batch_size= for the
truncated-Poisson variant; calibration must use
dpsgd_acc.poisson(..., truncated_batch_size=..., dataset_size=...)
to match.
5. Optimizer¶
from opake.optimizers import adamw
optimizer = adamw(
lr=1e-3,
weight_decay=0.0,
noise_bias_correction=True, # DP-aware second-moment correction
)
opt_state = optimizer.init(params)
opake.optimizers ships adamw, adam, sgd, radam,
adafactor, lion, ademamix, schedule_free, plus a few torchopt
re-exports. The noise_bias_correction=True flag corrects the
biased second moment that arises when the optimizer sees noised
gradients.
6. End-to-end loop¶
import torch
import torchopt
from torch.utils.data import DataLoader
from opake.functional import make_functional
from opake.serialization import state_dict
fmodel, params = make_functional(model)
loader = DataLoader(dataset, batch_sampler=sampler)
accountant = acc.Accountant()
step_process = dpsgd_acc.poisson(
dpsgd_acc.gaussian(noise_multiplier), sample_rate,
)
for batch in loader:
grads, clip_state = grad_fn(params, batch, state=clip_state)
noised, noise_state = noise_fn(grads, noise_state)
updates, opt_state = optimizer.update(noised, opt_state, params=params)
params = torchopt.apply_updates(params, updates)
accountant |= step_process
# Checkpoint at the end (or any step):
ckpt = {
"params": params,
"opt_state": opt_state,
"clip_state": clip_state,
"noise_state": noise_state,
"accountant": accountant,
}
torch.save(state_dict(ckpt), "step.pt")
Restore from the same flat state dict with
opake.serialization.from_state_dict.
Runnable references¶
examples/train_dpsgd.py— full causal-LM training script.tests/integration/test_dpsgd_pipeline.py— minimal smoke test exercising the same flow on a tiny LlamaConfig + LoRA model (and the Qwen2 variant for the real-HF case).
See also¶
- Clipping — fixed, AUTO-S, adaptive variants and per-group norms.
- Noise — Gaussian noise, when to choose which.
- Sampling — Poisson sampler details and the truncated-Poisson trade-off.
- Accounting —
DpProcess, calibration, budgets. - Optimizers — DP bias correction and the second-moment story.
- DP-SGD mechanisms — Gaussian reference page.
- DP-FTRL end-to-end — the correlated-noise companion pipeline.