Skip to main content

EMA - Pytorch

A simple way to keep track of an Exponential Moving Average (EMA) version of your pytorch model

Install

$ pip install ema-pytorch

Usage

import torch
from ema_pytorch import EMA

# your neural network as a pytorch module

net = torch.nn.Linear(512, 512)

# wrap your neural network, specify the decay (beta)

ema = EMA(
    net,
    beta = 0.9999,              # exponential moving average factor
    update_after_step = 100,    # only after this number of .update() calls will it start updating
    update_every = 10,          # how often to actually update, to save on compute (updates every 10th .update() call)
)

# mutate your network, with SGD or otherwise

with torch.no_grad():
    net.weight.copy_(torch.randn_like(net.weight))
    net.bias.copy_(torch.randn_like(net.bias))

# you will call the update function on your moving average wrapper

ema.update()

# then, later on, you can invoke the EMA model the same way as your network

data = torch.randn(1, 512)

output     = net(data)
ema_output = ema(data)

# if you want to save your ema model, it is recommended you save the entire wrapper
# as it contains the number of steps taken (there is a warmup logic in there, recommended by @crowsonkb, validated for a number of projects now)
# however, if you wish to access the copy of your model with EMA, then it will live at ema.ema_model

In order to use the post-hoc synthesized EMA, proposed by Karras et al. in a recent paper, follow the example below

import torch
from ema_pytorch import PostHocEMA

# your neural network as a pytorch module

net = torch.nn.Linear(512, 512)

# wrap your neural network, specify the sigma_rels or gammas

emas = PostHocEMA(
    net,
    sigma_rels = (0.05, 0.28),           # a tuple with the hyperparameter for the multiple EMAs. you need at least 2 here to synthesize a new one
    update_every = 10,                  # how often to actually update, to save on compute (updates every 10th .update() call)
    checkpoint_every_num_steps = 10,
    checkpoint_folder = './post-hoc-ema-checkpoints'  # the folder of saved checkpoints for each sigma_rel (gamma) across timesteps with the hparam above, used to synthesizing a new EMA model after training
)

net.train()

for _ in range(1000):
    # mutate your network, with SGD or otherwise

    with torch.no_grad():
        net.weight.copy_(torch.randn_like(net.weight))
        net.bias.copy_(torch.randn_like(net.bias))

    # you will call the update function on your moving average wrapper

    emas.update()

# now that you have a few checkpoints
# you can synthesize an EMA model with a different sigma_rel (say 0.15)

synthesized_ema = emas.synthesize_ema_model(sigma_rel = 0.15)

# output with synthesized EMA

data = torch.randn(1, 512)

synthesized_ema_output = synthesized_ema(data)

For testing out the claims of a free lunch from the Switch EMA paper, just set update_model_with_ema_every as so

ema = EMA(
    net,
    ...,
    update_model_with_ema_every = 10000 # say 10k steps is 1 epoch
)

# or you can do it manually at the end of each epoch

ema.update_model_with_ema()

Target Representation Routing

When dealing with nested module trees (e.g. self-supervised learning), you can wrap your network with EMAModuleWrapper.

The specified online submodules will automatically receive the target EMA submodules' outputs passed directly into their forward pass keyword arguments (defaulting to ema_output, or customized per module).

import torch
import torch.nn as nn
import torch.nn.functional as F

from ema_pytorch import EMAModuleWrapper

class Block(nn.Module):
    def __init__(self, dim):
        super().__init__()
        self.net = nn.Linear(dim, dim)
        self.proj = nn.Linear(dim, dim)
        self.register_buffer('zero', torch.tensor(0.), persistent = False)

    def forward(self, x, ema_output = None):
        h = F.relu(self.net(x))

        if ema_output is None:
            return h, self.zero

        if isinstance(ema_output, tuple):
            ema_output, _ = ema_output

        pred = self.proj(h)
        loss = 1. - F.cosine_similarity(pred, ema_output, dim = -1).mean()

        return h, loss

class NestedBranch(nn.Module):
    def __init__(self, dim):
        super().__init__()
        self.block1 = Block(dim)
        self.block2 = Block(dim)

    def forward(self, x):
        x, loss1 = self.block1(x)
        x, loss2 = self.block2(x)
        return x, loss1 + loss2

class DoubleNestedModel(nn.Module):
    def __init__(self, dim = 512):
        super().__init__()
        self.branch_a = NestedBranch(dim)
        self.branch_b = NestedBranch(dim)

    def forward(self, x):
        h_a, loss_a = self.branch_a(x)
        h_b, loss_b = self.branch_b(h_a)
        return h_b, loss_a + loss_b

model = DoubleNestedModel()

# branch_a nested blocks predict branch_b EMA teacher outputs

ema = EMAModuleWrapper(
    model,
    beta = 0.99,
    ema_module_kwargs = {
        'branch_a.block1': 'branch_b.block1',
        'branch_a.block2': 'branch_b.block2'
    }
)

x = torch.randn(2, 512)

# forwarding automatically injects EMA outputs into specified submodules

out, loss = ema(x)

loss.backward()
ema.update()

You can also customize the keyword argument name, transform captured outputs, or define custom submodule mappings:

ema = EMAModuleWrapper(
    model,
    beta = 0.99,
    ema_module_kwargs = {
        'deep.nested.branch_a': {
            'ema_module_path': 'deep.nested.branch_b',
            'ema_kwarg': 'teacher_latent'
        }
    }
)

For multi-view SSL (where student and teacher receive different augmented views), pass ema_args or ema_kwargs:

out, loss = ema(student_input, ema_args = teacher_input)

Citations

@article{Karras2023AnalyzingAI,
    title   = {Analyzing and Improving the Training Dynamics of Diffusion Models},
    author  = {Tero Karras and Miika Aittala and Jaakko Lehtinen and Janne Hellsten and Timo Aila and Samuli Laine},
    journal = {ArXiv},
    year    = {2023},
    volume  = {abs/2312.02696},
    url     = {https://api.semanticscholar.org/CorpusID:265659032}
}
@article{Lee2024SlowAS,
    title   = {Slow and Steady Wins the Race: Maintaining Plasticity with Hare and Tortoise Networks},
    author  = {Hojoon Lee and Hyeonseo Cho and Hyunseung Kim and Donghu Kim and Dugki Min and Jaegul Choo and Clare Lyle},
    journal = {ArXiv},
    year    = {2024},
    volume  = {abs/2406.02596},
    url     = {https://api.semanticscholar.org/CorpusID:270258586}
}
@article{Li2024SwitchEA,
    title   = {Switch EMA: A Free Lunch for Better Flatness and Sharpness},
    author  = {Siyuan Li and Zicheng Liu and Juanxi Tian and Ge Wang and Zedong Wang and Weiyang Jin and Di Wu and Cheng Tan and Tao Lin and Yang Liu and Baigui Sun and Stan Z. Li},
    journal = {ArXiv},
    year    = {2024},
    volume  = {abs/2402.09240},
    url     = {https://api.semanticscholar.org/CorpusID:267657558}
}

Metadata

Release files for ema-pytorch 0.8.3

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for ema-pytorch 0.8.3
File Size Uploaded
ema_pytorch-0.8.3.tar.gz 15.6 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for ema-pytorch 0.8.3
File Interpreter ABI Platform
ema_pytorch-0.8.3-py3-none-any.whl Python 3 none any Details

Total release size: 32.2 kB

Release files / ema_pytorch-0.8.3.tar.gz

Download URL ema_pytorch-0.8.3.tar.gz
Size 15.6 kB
Tags Source
SHA-256 checksum
How to use checksums
82600dd5da86aff3ac2ff641b9df206bc727da6468cb2901bfdab6afd36befb8
BLAKE2b-256 checksum
How to use checksums
8cf1bdc2e5a0da717e26a2ebd94f4e3ed5d3140cc5c2ae3335620bf2d9813602
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.8.17

Release files / ema_pytorch-0.8.3-py3-none-any.whl

Download URL ema_pytorch-0.8.3-py3-none-any.whl
Size 16.6 kB
Tags Python 3
SHA-256 checksum
How to use checksums
9035ec4d51f5af6b357ea4452232f334a3f04e9e943281dedf676e88aa372363
BLAKE2b-256 checksum
How to use checksums
4e36d2d009d6a5e268da748f1b26fb7d09190922e19827680d16e8b3db82cd84
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.8.17

Release history Release notifications | RSS feed

This release

0.8.3 This release

2 release files

0.8.2

2 release files

0.8.1

2 release files

0.8.0

2 release files

0.7.9

2 release files

0.7.8

2 release files

0.7.7

2 release files

0.7.6

2 release files

0.7.5

2 release files

0.7.4

2 release files

0.7.3

2 release files

0.7.2

2 release files

0.7.1

2 release files

0.7.0

2 release files

0.6.5

2 release files

0.6.4

2 release files

0.6.3

2 release files

0.6.2

2 release files

0.6.1

2 release files

0.6.0

2 release files

0.5.3

2 release files

0.5.2

2 release files

0.5.1

2 release files

0.5.0

2 release files

0.4.8

2 release files

0.4.7

2 release files

0.4.6

2 release files

0.4.5

2 release files

0.4.4

2 release files

0.4.3

2 release files

0.4.2

2 release files

0.4.1

2 release files

0.4.0

2 release files

0.3.3

2 release files

0.3.2

2 release files

0.3.1

2 release files

0.3.0

2 release files

0.2.6

2 release files

0.2.5

2 release files

0.2.4

2 release files

0.2.3

2 release files

0.2.2

2 release files

0.2.1

2 release files

0.2.0

2 release files

0.1.4

2 release files

0.1.2

2 release files

0.1.1

2 release files

0.1.0

2 release files

0.0.10

2 release files

0.0.9

2 release files

0.0.8

2 release files

0.0.7

2 release files

0.0.6

2 release files

0.0.5

2 release files

0.0.4

2 release files

0.0.3

2 release files

0.0.2

2 release files

0.0.1

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page