TrainingArguments¶
opake.transformers.TrainingArguments mirrors the subset of
Hugging Face TrainingArguments DPTrainer honors, plus DPTrainer's
own privacy / clipping / sampling / patching fields. Unsupported HF
knobs are intentionally omitted from the surface.
This page covers the fields you'll touch most often, with the non-obvious semantics called out. For the complete field inventory with every type / default / coercion rule, see API reference — transformers.
Batch-size contract¶
DPTrainer interprets batch-size args differently from stock HF. Read this once before tuning:
per_device_train_batch_sizeis the per-rank logical Poisson batch — the expected sample size drawn on each rank for one DP-SGD step. (This matches the HF interpretation when gradient accumulation is one step — DPTrainer is permanently in that regime; see below.)- Cluster-wide logical batch is
per_device_train_batch_size * world_size(exposed as the HF propertytrain_batch_size). The sample rateq = train_batch_size / N_totaldrives privacy accounting. - Internal microbatch chunking is only activated by
auto_find_microbatch_size=Trueon OOM retry — it splits the per-rank logical batch into smaller vmap calls without changing the logical batch or the sample rate (privacy-neutral). The same flag also halvesper_device_eval_batch_sizeon an eval/predict CUDA-OOM (eval has no grad-accumulation, so shrinking it is privacy-neutral). - When
per_device_eval_batch_sizeis omitted, it defaults to an explicitmicrobatch_sizeso eval's single batched forward stays within the configured physical memory bound. Without a configured microbatch, it defaults toper_device_train_batch_size. Set an explicit eval batch size to choose a different evaluation-throughput tradeoff.
To grow the effective batch, raise per_device_train_batch_size (the
expected Poisson round size); the physical vmap chunk (microbatch_size,
auto-shrunk under auto_find_microbatch_size) is decoupled from it and
privacy-neutral.
Privacy targets¶
The user-facing DP budget knobs:
args = TrainingArguments(
privacy_target_epsilon=8.0,
privacy_target_delta=1e-5, # default: 1 / dataset_size**1.1
clipping_norm=1.0, # scalar global clip, or per-group dict
privacy_noise_multiplier=None, # None ⇒ calibrate from epsilon
)
privacy_target_epsilon is the search target; calibration finds the
smallest noise multiplier that achieves it under the configured
sampler + composition. Set privacy_noise_multiplier directly to
skip calibration.
At least one of privacy_noise_multiplier / privacy_target_epsilon
must be set. Neither has a silent default — construction raises if
both are None. The usual configurations are:
privacy_target_epsilon=ε(NM left asNone) — calibrate the noise multiplier from the budget attrain()start and enable privacy accounting.privacy_noise_multiplier=σ(target_eps left asNone) — fixed-noise training. Independently composed DP-SGD runs skip privacy accounting by default; setprivacy_accounting=Trueto report the resulting ε without constraining it. Complete-horizon configurations (matrix-factorization mechanisms, k-out-of-t sampling) keep accounting on.- For independent DP-SGD only, set both to use a fixed multiplier and stop after the accumulated epsilon reaches the target.
Privacy accounting is on by default whenever privacy_target_epsilon is set
or the noise mechanism accounts over the complete horizon; only
independently composed fixed-noise DP-SGD runs default to off, where
privacy_accounting=False is also an explicit opt-out. Disabled runs do not
report ε / δ or write accountant.json checkpoints. Setting
privacy_accounting=False with a complete-horizon mechanism raises: the
horizon snapshot is what lets a resumed run detect a changed noise strategy
or multiplier, so it is always recorded.
Setting privacy_noise_multiplier=0.0 together with a
privacy_target_epsilon raises: the non-private path can't honour a
finite ε target, and silently dropping one of them would hide a
configuration mistake.
Stop-at-ε¶
When privacy_target_epsilon is set alongside a non-zero
privacy_noise_multiplier, training halts after the first accounted step
where the accumulated ε from the privacy accountant meets or exceeds the
target. The halt records state.privacy_target_epsilon_reached
= True and surfaces as a normal early-stop control flow (callbacks see
the final eval / save / log pass). The crossing step is predicted before the
loop and enforced independently of logging_strategy and logging_steps.
This mode is supported only for independent DP-SGD mechanisms. Horizon
mechanisms reject this combination because privacy-based early stopping is
unsupported.
A resume against a checkpoint where the budget is already spent
short-circuits before the first training step.
clipping_norm accepts a positive scalar (global clipping), a dict
keyed by regex on parameter names with a "fallback" entry
(per-group clipping), or a JSON / key=value,... string with the
same shape. It also accepts math.inf to disable clipping (the
single canonical no-clip value) — see the non-private baseline below.
Non-private baseline¶
Set privacy_noise_multiplier=0.0 to run the same trainer, mechanism,
sampler, and accounting surface with no privacy (ε = ∞) — a useful
baseline for measuring the utility cost of the DP machinery:
import math
args = TrainingArguments(
privacy_noise_multiplier=0.0, # no noise (σ = 0)
clipping_norm=math.inf, # optional: disable clipping too
)
- No noise — the realized noise standard deviation is
noise_multiplier × clip = 0, so gradients pass through unchanged. - ε = ∞ — the accountant composes a non-private step, so
metrics["privacy_epsilon"]is reported asinf(a faithful "no guarantee" output, not an error). Calibration is skipped and the resolved multiplier is recorded as0.0(source"fixed"). - Clipping is independent. It stays on by default (the configured
clipping_norm/clipping_mode); keep it for a "clipping-only" ablation, or disable it withclipping_norm=math.inffor plain non-private SGD.
This works for both "gaussian" (DP-SGD) and the mf_* (DP-FTRL)
mechanisms. Disabling clipping (clipping_norm=math.inf) is rejected
unless privacy_noise_multiplier=0.0, since infinite sensitivity with
noise would yield infinite noise and NaN gradients.
Sampling and noise¶
| Field | Use |
|---|---|
sampling_mode |
"auto" (default) pairs the sampler with privacy_noise_mechanism; accepted explicit values are listed per mechanism below. |
sampling_kwargs |
Mode-specific sampler kwargs. truncated_batch_size=N caps Poisson draws at N; k-out-of-t accepts k and allocation; other modes accept no keys. |
clipping_mode |
"fixed" (default), "adaptive", or "auto". adaptive is rejected under any mf_* mechanism (MF noise requires constant per-step sensitivity). |
clipping_kwargs |
Mode-specific clipping kwargs. Adaptive accepts the public factory names target_quantile and clipping_norm_max. AUTO-S accepts gamma; fixed accepts no keys. |
privacy_noise_mechanism |
"gaussian" (default, DP-SGD), or one of the DP-FTRL matrix-factorization mechanisms: "mf_band", "mf_blt", "mf_bisr", "mf_bsr", "mf_lambda_cgd", "mf_identity". |
privacy_noise_mechanism_kwargs |
Mechanism extras. For "gaussian": compute_dtype as a native torch.dtype or the string names float16, bfloat16, float32, float64. For mf_*: per-strategy kwargs (auto-filled from Mellum-shaped defaults — see below). |
noise_calibration_kwargs |
Calibration search bounds; defaults {"min": 0.11, "max": 10.0, "tolerance": 1e-3}. |
All dict-shaped fields accept a Mapping, a JSON object string, or
the HF-style comma string "a=1,b=2".
DP-FTRL mechanisms¶
Picking a mf_* mechanism auto-resolves the sampler and auto-fills
the strategy kwargs:
privacy_noise_mechanism |
Accepted samplers (auto first) | Default kwargs |
|---|---|---|
mf_band |
cyclic_poisson (or explicit b_min_sep) |
{"bands": 16} |
mf_blt |
balls_in_bins |
{"max_buffers": 10} |
mf_bisr |
balls_in_bins |
{"bandwidth": 4} |
mf_bsr |
balls_in_bins |
{"bandwidth": 8, "alpha": 1.0, "beta": 0.9} |
mf_lambda_cgd |
balls_in_bins |
{"lambda_": 0.5} |
mf_identity |
poisson (or explicit balls_in_bins) |
{} |
"auto" remains Poisson for Gaussian and identity MF. Gaussian explicitly
accepts sampling_mode="k_out_of_t". For exact block accounting, set
sampling_kwargs={"k": k, "allocation": "block"} so each example participates
once in each of k nearly equal blocks. With "allocation": "total", each
example chooses a uniform k-subset of the horizon and DPTrainer uses the
conservative block bound.
Identity MF explicitly accepts sampling_mode="balls_in_bins". Horizon modes
are accounted once for their declared n_steps; every privacy metric reported
during the run is the conservative full-horizon epsilon.
mf_band's cyclic_poisson sampler partitions the dataset into bands
disjoint groups and rotates one active group per step (Choquette-Choo et al.
2023, Theorem 1 / Algorithm 2). sample_rate here is expected_batch_size /
len(train_dataset) — the global fraction of the whole dataset drawn per
round — but the sampler and accountant both operate on the conditional
rate within the active group, whose size is the exact per-group population
floor(dataset_size / bands) (or, under DDP, floor((dataset_size //
world_size) / bands), since each rank partitions its own shard
independently — a group's remainder examples never participate).
DPTrainer converts consistently for both the runtime sampler and the
accountant, and rejects the configuration if the resulting conditional
rate exceeds 1 (lower bands or expected_batch_size). Plain
sampling_mode="poisson" (whole-dataset subsampling every step, ignoring
bands) is not a valid override for mf_band: it does not realize the
grouped participation pattern cyclic_poisson accounting assumes. Use the
explicit sampling_mode="b_min_sep" alternative (Dong & Ganesh
2026) if cyclic rotation does not fit
your data pipeline.
So the minimal DP-FTRL configuration is one field:
args = TrainingArguments(privacy_noise_mechanism="mf_band")
# sampling_mode resolves to "cyclic_poisson"
# privacy_noise_mechanism_kwargs resolves to {"bands": 16}
Defaults are tuned for a Mellum/Kstack-shaped causal-LM target;
they're a sensible starting point, not universally optimal. Override
any kwarg explicitly via privacy_noise_mechanism_kwargs={...}; user
keys win on collision with the defaults table.
Mechanism constraints (validated at construction):
- BandMF requires
bands <= total_steps; shrinkbandsfor short runs. - BallsInBins requires
total_steps % num_bins == 0wherenum_bins = expected_steps_per_epoch(so configuremax_steps/ dataset / batch size to satisfy this). - BSR requires
alpha > beta(paper constraint).
Compute / precision¶
| Field | Default | Notes |
|---|---|---|
bf16 |
False |
bf16 autocast on the per-example loss closure. |
bf16_full_eval |
False |
Cast the model to bf16 for the eval scope only. |
gradient_checkpointing |
False |
Opake automatically uses the vmap-safe non-reentrant path; no checkpointing kwargs are required. Incompatible with torch_compile. |
torch_compile |
False |
Strictly compiles each tensor-only vmap(grad)+clip+reduce chunk with automatic dynamic shapes. On a Dynamo failure, warns once and runs the affected chunk eagerly thereafter. |
cuda_graphs |
0 |
Maximum distinct graph signatures retained per trainer. Zero disables graph replay; positive values enable replay and set the cap. See CUDA-graph replay. |
CUDA-graph replay¶
cuda_graphs > 0 captures the per-microbatch kernel (vmap(grad), per-example
clipping and the batch reduction) once per distinct input signature, then replays
that signature. The configured value caps the number of signatures retained;
cuda_graphs=0 disables replay. A signature includes tensor shapes, strides,
dtypes, devices, requires_grad, and non-tensor arguments; sequence-length
changes can therefore capture new graphs even when the microbatch size stays
fixed.
CUDA graphs primarily reduce Python and kernel-launch overhead; they do not make the captured attention, GEMM, or other GPU kernels intrinsically faster. They are most promising when each microbatch does relatively little GPU work but launches many operations—for example, short sequences, small adapters, or small models with a high kernel-launch count. They are less likely to help when long sequences or large adapters make the step GPU-kernel-bound. Profile a matched eager run rather than assuming that a different shape or enabling graphs will improve throughput.
For SFT/DPO, pad_to_multiple_of rounds each already-collated logical batch's
maximum sequence length up to the chosen multiple; it does not group examples
by length. Setting max_length and pad_to_multiple_of to the same value (for
example, both 4096) gives one padded sequence width and can avoid repeated
captures caused by varying lengths, at the cost of padding shorter batches to
that width. A smaller multiple creates coarse width buckets (e.g. multiples of
512 up to a 4096 cap can produce up to eight widths); these may reduce wasted
padding on shorter batches but can require more retained graphs. Choose a
multiple that divides max_length to keep padding within that cap.
- The first occurrence of each signature includes capture time. Each retained signature keeps graph-private buffers alive, so memory can grow with the number of captures.
- Once the
cuda_graphslimit is reached, unseen signatures run eagerly; the cap does not evict existing graphs, stabilize shapes, or guarantee a speedup. Lower the count for large models/adapters when memory is the limiting concern. - Every tensor input, including the current clipping threshold and the parameters, is copied into the graph before each replay. Python state that the per-example loss reads other than through its arguments is frozen at capture.
- CUDA only. Mutually exclusive with
torch_compileand incompatible withauto_find_microbatch_sizeandactivation_offloading. - With
gradient_checkpointing, the model must have no active dropout: the checkpoint recompute runs without saved RNG state (preserve_rng_state=False). - The fused linear cross-entropy (
loss_type="chunked_nll") processes ignored tokens with zero weight instead of compacting them, because compaction needs a host synchronization. - Clipping, noise, sampling and accounting are unchanged. Results can differ from eager execution in the last bits, within the run-to-run variation of the eager kernels.
Patches and kernels¶
Three flags drive the model patching at trainer construction time:
| Field | Default | Effect |
|---|---|---|
use_compat_patches |
True |
vmap-safety patches (eager attention, batchify, vmap-safe masking / collator / checkpoint hooks). |
use_performance_kernels |
False |
CUDA + Triton kernel group (RoPE, RMSNorm, SwiGLU/GeGLU, cross-entropy). Auto-False on hosts without CUDA + Triton. |
performance_kernels_config |
None |
Flat dict forwarded as kwargs to apply_model_patches / apply_runtime_patches — per-key override. |
If fused CE prevents full-graph capture, performance_kernels_config={"fused_linear_cross_entropy": False} disables it at the cost of higher logits memory use.
See Model patches for the full configuration matrix.
Saving and resume¶
Standard HF save fields work as expected:
| Field | Default | Effect |
|---|---|---|
save_strategy |
"steps" |
One of {"no", "steps", "epoch", "best"}. "best" auto-injects BestModelSaveCallback. |
save_steps / save_total_limit / save_safetensors |
— | Standard HF semantics. |
save_on_each_node |
False |
Every node's rank-0 writes a copy (for node-local storage). |
save_only_model |
False |
Skip the DP runtime bundle (optimizer / sampler / RNG); ships weights + accountant.json only. |
load_best_model_at_end |
False |
Restore the best-eval checkpoint after train(). Raises if no improving step was recorded. |
Resume claims:
train(resume_from_checkpoint=<path>)restores model weights, optimizer / clip / noise state, sampler cursor, RNG snapshots, and the privacy accountant in one call.- For independent DP-SGD, the saved accountant contains the executed composition and calibration covers the remaining steps against it. Horizon mechanisms retain their single declared full-horizon process.
resume_from_checkpointrequires a complete DP checkpoint (dp_state.pt+dp_optimizer.pt+accountant.json). A weights-only export (save_only_model=True, an HF checkpoint, a pretrained model) is rejected — to start a fresh DP run from such weights, load them at construction (model=...); the run begins with a zero accountant, which is correct only when the prior training had no DP cost (e.g. public-data warmup).ignore_data_skip=Trueskips sampler-state restore. Poisson resumes use a distinct stream; participation samplers require the saved cursor.restore_callback_states_from_checkpoint=Truereads saved callback state and copies attributes back onto the live callback instances (e.g.EarlyStoppingCallback's patience counter).
Evaluation¶
| Field | Default | Effect |
|---|---|---|
eval_strategy |
"no" |
One of {"no", "steps", "epoch"}. |
eval_steps |
None |
Falls back to logging_steps when eval_strategy="steps" and unset. |
prediction_loss_only |
False |
Skip logits / labels materialisation; only loss reaches compute_metrics. |
include_for_metrics |
[] |
Subset of {"inputs", "loss"}. "loss" switches to the vmap'd per-example eval (real per-example losses). |
metric_for_best_model |
None |
Required when save_strategy="best" or load_best_model_at_end=True. Auto-prefixed with eval_ if missing. |
Distributed¶
local_rank,ddp_backend,ddp_timeoutfollow stock HF.average_tokens_across_devices=True(default) — averages per-rank token counts into a cluster-wide total fornum_input_tokens_seen/train_tokens_per_second.- DPTrainer is DDP-only;
DataParallelis not supported.
For per-rank sharding, accountant cluster-wide composition, and rank-gated checkpointing, see Distributed DPTrainer.
Converting from HF / TRL configs¶
Rather than hand-port an upstream config, convert it with the classmethod on the matching opake config:
from opake.transformers import TrainingArguments
args = TrainingArguments.from_hf(hf_args, privacy_target_epsilon=8.0)
TrainingArguments.from_hf(hf_args, …)— fromtransformers.TrainingArgumentsSFTConfig.from_trl(trl_cfg, …)— fromtrl.SFTConfigDPOConfig.from_trl(trl_cfg, …)— fromtrl.DPOConfig
(SFTConfig / DPOConfig live in opake.transformers.trl.)
What the converter does:
- renames legacy names (
per_gpu_train_batch_size,lr_scheduler_type, …); - collapses the HF effective batch (
per_device_train_batch_size × gradient_accumulation_steps) into the logical Poisson batch, withper_device_train_batch_sizebecoming the vmapmicrobatch_sizeandauto_find_batch_size → auto_find_microbatch_size; - loosely maps
max_grad_norm → clipping_norm(no warning — pass an explicitclipping_norm=to override); - remaps optimizers (
adamw_torch/adamw_hf/adamw_torch_fused → adamw,adafactor=True → optim="adafactor") anduse_liger_kernel → use_performance_kernels; - drops irrelevant fields (with a
RuntimeWarningwhen non-default), and raises with a per-field rationale on unsupported ones (fp16,fsdp, paged optimizers, …).
A DP knob is required as an override (privacy_noise_multiplier= or
privacy_target_epsilon=) — upstream configs carry no privacy budget. Any
other keyword overrides the converted field by name after translation
(e.g. use_performance_kernels=True); performance kernels default OFF on
conversion to match HF/TRL since their default can't be distinguished from an
explicit value.
Migrating from HF: unsupported arguments¶
TrainingArguments is a standalone dataclass, not a subclass of
transformers.TrainingArguments, so passing an unsupported HF knob to the
constructor raises TypeError (the converters above translate or drop
these for you). For reference, the notable ones:
| HF argument | Why it's unsupported | DPTrainer alternative |
|---|---|---|
group_by_length, length_column_name |
Length-bucketed batching breaks the equal per-example inclusion probability Poisson amplification relies on | Leave examples unsorted; Poisson sampling handles variable lengths |
dataloader_drop_last |
The Poisson / random samplers produce variable-size batches, so dropping a "last batch" is meaningless; the sequential batch sampler already enforces drop-last internally where it matters for correctness | n/a (handled by the sampler) |
deepspeed, fsdp, fsdp_config, accelerator_config, parallelism_config |
Parameter/gradient sharding is incompatible with vmap per-example gradients | Use Opake's built-in DDP (torchrun + sharded data) |
tpu_num_cores, mp_parameters |
TPU/XLA and SageMaker MP are not supported execution backends | CUDA / CPU only |
fp16, fp16_full_eval, fp16_opt_level, half_precision_backend, fp16_backend |
fp16 dynamic loss scaling adds a per-example unscale-before-clip step for no benefit on bf16-capable hardware | bf16=True (native bf16 autocast; no loss scaler) |
optim="adamw_8bit" / paged / Apex-fused |
No functional torchopt equivalent | A supported optim name (see the optimizer table) |
batch_eval_metrics |
Streaming metric reduction not implemented | Eval predictions offload to CPU after every batch by default. CUDA overlaps copies through a bounded pinned staging queue and CPU chunks merge through a balanced tree. Set eval_accumulation_steps to a larger window only to reduce transfer calls, accepting retention of up to that many batches on device. |
per_gpu_* and the deprecated push_to_hub_* aliases are renamed to their
modern equivalents; adafactor=True is mapped to optim="adafactor".
torchdynamo and other superseded flags are dropped — use their modern
replacements.
See also¶
- DPTrainer — what to call once the args are configured.
- Model patches — kernel and patch configuration.
- API reference — transformers — every field, type, default, and validation rule.