nvalchemi.training.hooks.EMAHook#
- class nvalchemi.training.hooks.EMAHook(*, model_key='main', decay=0.999, update_every=1, start_step=0, use_buffers=True, num_updates=0)[source]#
Hook maintaining an exponential moving average of a training model.
Runs through
TrainingUpdateOrchestratorand updates atTrainingStage.AFTER_OPTIMIZER_STEP. It lazily builds aAveragedModelwrapped aroundctx.models[model_key]on the first eligible step, and updates it viaget_ema_multi_avg_fn()— no manual parameter arithmetic. The hook is a pure observer: it never callsbackward(), touches gradients, drives any optimizer / scheduler /GradScaler, or mutatesctx.models. If an earlier update hook vetoesTrainingStage.DO_OPTIMIZER_STEP, the orchestrator passeswill_skip=Trueand EMA does not update on that batch.Access the averaged wrapper via
get_averaged_model(), which raises aRuntimeErrorif no eligible step has yet triggered lazy initialization. Adevice/dtypefield is omitted by design; afterAveragedModeldeep-copies the source, EMAHook aligns each averaged parameter and buffer to the corresponding source tensor’s device and floating-point dtype. This keeps generated or monkey-patched modules whose deepcopy/load path materializes registered tensors on CPU or in a default dtype usable without model-specific hooks.Note
If the copied module defines
modify_ema_methods(), the hook calls it once immediately after constructing the averaged model. Model wrappers can use this optional interface to restore runtime methods discarded bydeepcopy.- Parameters:
model_key (str, optional) – Key identifying the source model inside
ctx.models. Default"main".decay (float, optional) – EMA decay factor in
[0.0, 1.0). Default0.999.update_every (int, optional) – Positive step stride for averaging updates. Default
1.start_step (int, optional) – Non-negative minimum completed step before updates begin. Default
0.use_buffers (bool, optional) – Forwarded to
AveragedModel; whenTruealso averages module buffers. DefaultTrue.num_updates (int, optional) – Non-negative count of EMA updates already performed. Settable at construction so a checkpoint can restore the update counter; normally left at its default and advanced internally as updates occur. Default
0.
- Raises:
pydantic.ValidationError – If any field violates its declared bounds or an unknown kwarg is passed.
KeyError – On first eligible call, if
model_keyis missing fromctx.models.RuntimeError – From
get_averaged_model()when called before lazy init.
See also
torch.optim.swa_utils.AveragedModelUnderlying averaging wrapper.
torch.optim.swa_utils.get_ema_multi_avg_fnFactory for the EMA averaging function.
Examples
Checkpoint recipe for inference / eval reload of the EMA-averaged weights. Save
hook.get_averaged_model().modulealongside the base model and rebuild theAveragedModelwrapper after loading, becausecreate_model_spec()only reconstructs plainModuleobjects:>>> from torch import nn >>> from torch.optim.swa_utils import AveragedModel >>> from nvalchemi.training import ( ... EMAHook, create_model_spec, load_checkpoint, save_checkpoint, ... ) >>> base = nn.Linear(4, 2) >>> hook = EMAHook(model_key="main", decay=0.99) >>> # ... training loop drives `hook` via TrainingStrategy ... >>> spec = create_model_spec(nn.Linear, in_features=4, out_features=2) >>> save_checkpoint( ... "ckpt/", ... models={ ... "main": (base, spec), ... "main_ema": (hook.get_averaged_model().module, spec), ... }, ... ) >>> loaded = load_checkpoint("ckpt/") >>> reconstructed_ema = AveragedModel(loaded.models["main_ema"][0])
To resume training with EMA continuing from a checkpoint, use
state_dict()/load_state_dict(), which round-tripnum_updatesand the averaged weights into a freshly constructed hook.Notes
The default deepcopy-based construction does not support
fully_shard(FSDP2) / DTensor models; override_build_averaged_model()to supply a pre-built sharded copy.- get_averaged_model()[source]#
Return the
AveragedModelwrapper or raise if uninitialized.- Raises:
RuntimeError – If neither setup nor an eligible training step has initialized EMA.
- Return type:
- load_state_dict(state)[source]#
Restore hook counters and averaged weights from a prior snapshot.
- Parameters:
state (Mapping[str, Any]) – Mapping produced by
state_dict(). Missing config keys andnum_updatesare ignored. Missingaveraged_model_stateclears any prior live or pending averaged state. Any present config key must equal the corresponding constructor field.- Raises:
ValueError – If a config field in
statediffers from this hook’s current field.- Return type:
None
Notes
Before lazy init,
averaged_model_stateis stashed and applied during_ensure_initialized(). Clearing on absence prevents stale averaged state from surviving a config-only reload. Checkpoint loaders may still choose amap_location, but EMAHook reapplies per-tensor device and floating-point dtype placement after loading averaged state so registered tensors remain usable for validation.
- model_config = {'arbitrary_types_allowed': True, 'extra': 'forbid'}#
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- model_post_init(context, /)#
This function is meant to behave like a BaseModel method to initialise private attributes.
It takes context as an argument since that’s what pydantic-core passes when calling it.
- Parameters:
self (BaseModel) – The BaseModel instance.
context (Any) – The context.
- Return type:
None
- state_dict()[source]#
Return a serializable snapshot of hook state.
- Returns:
Contains the config fields,
num_updates, and — if available —averaged_model_statesourced from the liveAveragedModelor, before lazy init, from any stashed pending state. Nodevicekey is emitted.- Return type:
dict[str, Any]