Skip to content

Utilities

Use these helpers to find registered components, manage gradients, and offload optimizer work to the CPU.

get_supported_optimizers(filters=None)

List registered optimizer names in alphabetical order.

Parameters:

Name Type Description Default
filters str | list[str] | None

Wildcard pattern or list of patterns, such as '*adam*'. None selects all names.

None

Returns:

Type Description
list[str]

list[str]: Matching names in lowercase, without duplicates.

Source code in pytorch_optimizer/optimizer/__init__.py
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
def get_supported_optimizers(filters: str | list[str] | None = None) -> list[str]:
    """List registered optimizer names in alphabetical order.

    Args:
        filters: Wildcard pattern or list of patterns, such as `'*adam*'`. `None` selects all names.

    Returns:
        list[str]: Matching names in lowercase, without duplicates.

    """
    if filters is None:
        return sorted(OPTIMIZERS.keys())

    include_filters: Sequence[str] = filters if isinstance(filters, (tuple, list)) else [filters]

    filtered_list: set[str] = set()
    for include_filter in include_filters:
        filtered_list.update(fnmatch.filter(OPTIMIZERS.keys(), include_filter))

    return sorted(filtered_list)

get_supported_lr_schedulers(filters=None)

List registered scheduler names in alphabetical order.

Parameters:

Name Type Description Default
filters str | list[str] | None

Wildcard pattern or list of patterns, such as '*cosine*'. None selects all names.

None

Returns:

Type Description
list[str]

list[str]: Matching names in lowercase, without duplicates.

Source code in pytorch_optimizer/lr_scheduler/__init__.py
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
def get_supported_lr_schedulers(filters: str | list[str] | None = None) -> list[str]:
    """List registered scheduler names in alphabetical order.

    Args:
        filters: Wildcard pattern or list of patterns, such as `'*cosine*'`. `None` selects all names.

    Returns:
        list[str]: Matching names in lowercase, without duplicates.
    """
    if filters is None:
        return sorted(LR_SCHEDULERS.keys())

    include_filters: Sequence[str] = filters if isinstance(filters, (tuple, list)) else [filters]

    filtered_list: set[str] = set()
    for include_filter in include_filters:
        filtered_list.update(fnmatch.filter(LR_SCHEDULERS.keys(), include_filter))

    return sorted(filtered_list)

get_supported_loss_functions(filters=None)

List registered loss function names in alphabetical order.

Parameters:

Name Type Description Default
filters str | list[str] | None

Wildcard pattern or list of patterns, such as '*focal*'. None selects all names.

None

Returns:

Type Description
list[str]

list[str]: Matching names in lowercase, without duplicates.

Source code in pytorch_optimizer/loss/__init__.py
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
def get_supported_loss_functions(filters: str | list[str] | None = None) -> list[str]:
    """List registered loss function names in alphabetical order.

    Args:
        filters: Wildcard pattern or list of patterns, such as `'*focal*'`. `None` selects all names.

    Returns:
        list[str]: Matching names in lowercase, without duplicates.

    """
    if filters is None:
        return sorted(LOSS_FUNCTIONS.keys())

    include_filters: Sequence[str] = filters if isinstance(filters, (tuple, list)) else [filters]

    filtered_list: set[str] = set()
    for include_filter in include_filters:
        filtered_list.update(fnmatch.filter(LOSS_FUNCTIONS.keys(), include_filter))

    return sorted(filtered_list)

CPUOffloadOptimizer

Offload optimizer states and updates to the CPU for single GPU training.

Transfers gradients to pinned CPU memory and copies updated parameters back to the GPU.

Reference: https://github.com/pytorch/ao/blob/main/torchao/prototype/low_bit_optim/cpu_offload.py

Parameters:

Name Type Description Default
params ParamsT

Parameters to optimize or dictionaries defining parameter groups.

required
optimizer_class type[Optimizer]

Base optimizer class. Defaults to torch.optim.AdamW.

AdamW
offload_gradients bool

Free GPU gradients after transfer. Incompatible with gradient accumulation.

False
**kwargs dict

Options for the base optimizer, such as lr and weight_decay.

{}
Source code in pytorch_optimizer/optimizer/utils/offload.py
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
class CPUOffloadOptimizer:  # pragma: no cover
    """Offload optimizer states and updates to the CPU for single GPU training.

    Transfers gradients to pinned CPU memory and copies updated parameters back to the GPU.

    Reference: https://github.com/pytorch/ao/blob/main/torchao/prototype/low_bit_optim/cpu_offload.py

    Args:
        params: Parameters to optimize or dictionaries defining parameter groups.
        optimizer_class: Base optimizer class. Defaults to `torch.optim.AdamW`.
        offload_gradients: Free GPU gradients after transfer. Incompatible with gradient accumulation.
        **kwargs (dict): Options for the base optimizer, such as `lr` and `weight_decay`.

    """

    def __init__(
        self,
        params: ParamsT,
        optimizer_class: type[Optimizer] = torch.optim.AdamW,
        *,
        offload_gradients: bool = False,
        **kwargs,
    ) -> None:
        if optimizer_class is torch.optim.AdamW and TORCH_VERSION_AT_LEAST_2_4 and 'fused' not in kwargs:
            kwargs.update(fused=True)

        param_groups = list(params)
        if len(param_groups) == 0:
            raise ValueError('optimizer got an empty parameter list')

        if not isinstance(param_groups[0], dict):
            param_groups = [{'params': param_groups}]

        param_groups = cast(list[ParamGroup], param_groups)

        self.param_cuda2cpu_map: dict[torch.Tensor, torch.Tensor] = {}
        self.cpu_grad_buffers: dict[torch.Tensor, torch.Tensor] = {}
        self.stream = torch.cuda.Stream()

        self.queue = {}

        def backward_hook(p_cuda: torch.Tensor) -> None:
            if p_cuda.grad is None:
                return

            self.stream.wait_stream(torch.cuda.current_stream())
            with torch.cuda.stream(self.stream):
                self.cpu_grad_buffers[p_cuda].copy_(p_cuda.grad, non_blocking=True)

            if p_cuda in self.queue:
                del self.queue[p_cuda]

            self.queue[p_cuda] = self.stream.record_event()

            if offload_gradients:
                p_cuda.grad.record_stream(self.stream)
                p_cuda.grad = None

        cpu_groups = []

        for param_group in param_groups:
            group_params = param_group.get('params', None)
            if group_params is None:
                continue

            cpu_params = []

            for p_cuda in group_params:
                p_cpu = torch.empty_like(p_cuda, device='cpu', pin_memory=True)
                self.cpu_grad_buffers[p_cuda] = torch.empty_like(p_cpu, pin_memory=True)

                p_cpu.copy_(p_cuda.detach(), non_blocking=True)
                self.param_cuda2cpu_map[p_cuda] = p_cpu
                cpu_params.append(p_cpu)

                if p_cuda.requires_grad:
                    p_cuda.register_post_accumulate_grad_hook(backward_hook)

            cpu_groups.append({**param_group, 'params': cpu_params})

        self.optimizer = optimizer_class(cpu_groups, **kwargs)

    @torch.no_grad()
    def step(self, closure: Closure = None) -> Loss:
        loss = None
        if closure is not None:
            with torch.enable_grad():
                loss = closure()

        if self.queue:
            next(reversed(self.queue.values())).synchronize()

        for p_cuda, p_cpu in self.param_cuda2cpu_map.items():
            p_cpu.grad = self.cpu_grad_buffers[p_cuda] if p_cuda in self.queue else None

        self.optimizer.step()

        with torch.cuda.stream(self.stream):
            for p_cuda in self.queue:
                p_cuda.copy_(self.param_cuda2cpu_map[p_cuda], non_blocking=True)

        self.queue.clear()
        torch.cuda.current_stream().wait_stream(self.stream)

        return loss

    def zero_grad(self, set_to_none: bool = True) -> None:
        for p_cuda in self.param_cuda2cpu_map:
            if set_to_none:
                p_cuda.grad = None
            elif p_cuda.grad is not None:
                p_cuda.grad.zero_()

        self.queue.clear()

    @property
    def param_groups(self):
        return self.optimizer.param_groups

    def state_dict(self):
        return self.optimizer.state_dict()

    def load_state_dict(self, state_dict):
        if not isinstance(state_dict, list):
            self.optimizer.load_state_dict(state_dict)
            return

        if len(state_dict) != len(self.param_cuda2cpu_map):
            raise ValueError('offloaded optimizer state does not match the current parameters')

        merged = {'state': {}, 'param_groups': []}
        offset = 0

        for group in self.optimizer.state_dict()['param_groups']:
            saved_groups = []

            for index in group['params']:
                saved = state_dict[offset]
                saved_group = saved['param_groups'][0]
                saved_groups.append({key: value for key, value in saved_group.items() if key != 'params'})
                saved_key = saved_group['params'][0]

                if saved_key in saved['state']:
                    merged['state'][index] = saved['state'][saved_key]

                offset += 1

            if saved_groups:
                if any(saved_group != saved_groups[0] for saved_group in saved_groups[1:]):
                    raise ValueError('legacy offloaded parameter options differ within a parameter group')

                group = {**saved_groups[0], 'params': group['params']}

            merged['param_groups'].append(group)

        self.optimizer.load_state_dict(merged)

is_valid_parameters(parameters)

Check for a nonempty list or tuple whose first entry is a parameter group dictionary.

Source code in pytorch_optimizer/optimizer/utils/model.py
10
11
12
def is_valid_parameters(parameters: ParamsT) -> bool:
    """Check for a nonempty list or tuple whose first entry is a parameter group dictionary."""
    return isinstance(parameters, (list, tuple)) and len(parameters) > 0 and isinstance(parameters[0], dict)

has_overflow(grad_norm)

Return whether a tensor contains NaN or infinite values.

Source code in pytorch_optimizer/optimizer/utils/precision.py
5
6
7
def has_overflow(grad_norm: torch.Tensor) -> bool:
    """Return whether a tensor contains NaN or infinite values."""
    return bool(torch.logical_or(torch.isnan(grad_norm), torch.isinf(grad_norm)).any())

to_real(x)

Return the real part of a complex tensor, or the original real tensor.

Source code in pytorch_optimizer/optimizer/utils/precision.py
10
11
12
def to_real(x: torch.Tensor) -> torch.Tensor:
    """Return the real part of a complex tensor, or the original real tensor."""
    return x.real if torch.is_complex(x) else x

normalize_gradient(x, use_channels=False, epsilon=1e-08)

Divide gradients by their standard deviation in place.

Parameters:

Name Type Description Default
x Tensor

Gradient tensor to normalize.

required
use_channels bool

If True, perform channel wise normalization.

False
epsilon float

Small constant added for numerical stability.

1e-08
Source code in pytorch_optimizer/optimizer/utils/gradient.py
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
def normalize_gradient(x: torch.Tensor, use_channels: bool = False, epsilon: float = 1e-8) -> None:
    """Divide gradients by their standard deviation in place.

    Args:
        x: Gradient tensor to normalize.
        use_channels: If True, perform channel wise normalization.
        epsilon: Small constant added for numerical stability.

    """
    size: int = x.dim()
    if size > 1 and use_channels:
        if math.prod(x.shape[1:]) <= 1:
            return

        s = x.std(dim=tuple(range(1, size)), keepdim=True).add_(epsilon)
        x.div_(s)
    elif torch.numel(x) > 2:
        s = x.std().add_(epsilon)
        x.div_(s)

clip_grad_norm(parameters, max_norm=0.0, sync=False)

Compute the global L2 gradient norm and optionally clip gradients in place.

Parameters:

Name Type Description Default
parameters ParamsT | Tensor

Tensor or iterable of tensors whose gradients to inspect.

required
max_norm float

Maximum gradient norm. A nonpositive value disables clipping.

0.0
sync bool

Sum squared norms across the distributed process group for sharded gradients.

False

Returns:

Type Description
Tensor

torch.Tensor: Global gradient norm before clipping.

Source code in pytorch_optimizer/optimizer/utils/gradient.py
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
def clip_grad_norm(
    parameters: ParamsT | torch.Tensor,
    max_norm: float = 0.0,
    sync: bool = False,
) -> torch.Tensor:
    """Compute the global L2 gradient norm and optionally clip gradients in place.

    Args:
        parameters: Tensor or iterable of tensors whose gradients to inspect.
        max_norm: Maximum gradient norm. A nonpositive value disables clipping.
        sync: Sum squared norms across the distributed process group for sharded gradients.

    Returns:
        torch.Tensor: Global gradient norm before clipping.

    """
    if parameters is None:
        raise ValueError('ParamsT cannot be None.')

    if isinstance(parameters, torch.Tensor):
        parameters = [parameters]

    # make sure any generators are expanded
    parameters = cast(list, list(parameters))

    # if syncing we need to manually perform the clipping so that we aggregate properly
    if max_norm > 0 and not sync:
        return clip_grad_norm_(parameters, max_norm)

    norm_sq = get_global_gradient_norm([{'params': parameters}])
    if sync:  # pragma: no cover
        # also need to get the norms from all the other sharded works in FSDP
        all_reduce(norm_sq)

    grad_norm = norm_sq.sqrt_().squeeze_(0)
    if max_norm > 0:  # pragma: no cover
        clip_coefficient = (max_norm / (grad_norm + 1e-6)).clamp_(max=1.0)
        for p in parameters:
            if p.grad is not None:
                p.grad.detach().mul_(clip_coefficient.to(p.grad.device))

    return grad_norm

unit_norm(x, norm=2.0)

Compute parameter unit norms for adaptive gradient clipping.

Uses the full norm for scalars and vectors, dimension 1 for 2D and 3D tensors, and all dimensions after the first for tensors with four or more dimensions.

Parameters:

Name Type Description Default
x Tensor

Parameter or gradient tensor.

required
norm float

Order of the vector norm.

2.0

Returns:

Type Description
Tensor

torch.Tensor: Unit norms, with reduced dimensions retained for multidimensional inputs.

Source code in pytorch_optimizer/optimizer/utils/gradient.py
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
def unit_norm(x: torch.Tensor, norm: float = 2.0) -> torch.Tensor:
    """Compute parameter unit norms for adaptive gradient clipping.

    Uses the full norm for scalars and vectors, dimension 1 for 2D and 3D tensors,
    and all dimensions after the first for tensors with four or more dimensions.

    Args:
        x: Parameter or gradient tensor.
        norm: Order of the vector norm.

    Returns:
        torch.Tensor: Unit norms, with reduced dimensions retained for multidimensional inputs.

    """
    keep_dim: bool = True
    dim: int | tuple[int, ...] | None = None

    x_len: int = len(x.shape)
    if x_len <= 1:
        keep_dim = False
    elif x_len in (2, 3):
        dim = 1
    elif x_len == 4:
        dim = (1, 2, 3)
    else:
        dim = tuple(range(1, x_len))

    return x.norm(p=norm, dim=dim, keepdim=keep_dim)

disable_running_stats(model)

Pause BatchNorm running statistic updates by setting momentum to zero.

Source code in pytorch_optimizer/optimizer/utils/model.py
15
16
17
18
19
20
21
22
23
def disable_running_stats(model: nn.Module):
    """Pause BatchNorm running statistic updates by setting momentum to zero."""

    def _disable(module):
        if isinstance(module, _BatchNorm):
            module.backup_momentum = module.momentum
            module.momentum = 0

    model.apply(_disable)

enable_running_stats(model)

Restore BatchNorm momentum after pausing running statistic updates.

Source code in pytorch_optimizer/optimizer/utils/model.py
26
27
28
29
30
31
32
33
def enable_running_stats(model: nn.Module):
    """Restore BatchNorm momentum after pausing running statistic updates."""

    def _enable(module):
        if isinstance(module, _BatchNorm) and hasattr(module, 'backup_momentum'):
            module.momentum = module.backup_momentum

    model.apply(_enable)

get_global_gradient_norm(param_groups, device=None, *, weight_adaptive=False)

Return the sum of squared L2 gradient norms across parameter groups.

Parameters:

Name Type Description Default
param_groups list[ParamGroup] | None

Optimizer parameter groups, or None for no parameters.

required
device device | None

Output device. Defaults to the first parameter's device, or CPU for empty groups.

None
weight_adaptive bool

Weight gradients by absolute parameter values for groups with adaptive=True.

False

Returns:

Type Description
Tensor

torch.Tensor: Squared global norm as a single element float32 tensor.

Source code in pytorch_optimizer/optimizer/utils/gradient.py
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
@torch.no_grad()
def get_global_gradient_norm(
    param_groups: list[ParamGroup] | None,
    device: torch.device | None = None,
    *,
    weight_adaptive: bool = False,
) -> torch.Tensor:
    """Return the sum of squared L2 gradient norms across parameter groups.

    Args:
        param_groups: Optimizer parameter groups, or None for no parameters.
        device: Output device. Defaults to the first parameter's device, or CPU for empty groups.
        weight_adaptive: Weight gradients by absolute parameter values for groups with `adaptive=True`.

    Returns:
        torch.Tensor: Squared global norm as a single element float32 tensor.

    """
    param_groups = param_groups or []
    device = device or next((p.device for group in param_groups for p in group['params']), torch.device('cpu'))
    grads: list[torch.Tensor] = []
    for group in param_groups:
        adaptive = weight_adaptive and group.get('adaptive', False)
        for p in group['params']:
            if p.grad is None:
                continue

            grad = p.grad
            if adaptive:
                if grad.dtype in (torch.float16, torch.bfloat16):
                    grad = grad.float()
                grad = grad * p.abs()

            grads.append(grad)

    if not grads:
        return torch.zeros(1, dtype=torch.float32, device=device)

    norms = (
        torch._foreach_norm(grads)
        if has_foreach_support(grads) and grads[0].dtype not in (torch.float16, torch.bfloat16)
        else [(grad.float() if grad.dtype in (torch.float16, torch.bfloat16) else grad).norm() for grad in grads]
    )
    squared_norms = torch.stack([norm.to(device) for norm in norms]).float().square_()
    return squared_norms.sum().reshape(1)

reg_noise(network1, network2, num_data, lr, eta=0.008, temperature=0.0001)

Compute the Entropy-MCMC coupling and noise term for two networks.

Usage example and detailed implementation can be found at: https://github.com/lblaoke/EMCMC/blob/master/exp/cifar10_emcmc.py

Parameters:

Name Type Description Default
network1 Module

First neural network.

required
network2 Module

Second neural network.

required
num_data int

Number of training data points.

required
lr float

Learning rate.

required
eta float

Eta parameter controlling auxiliary guiding variable.

0.008
temperature float

Temperature parameter for sampling.

0.0001
Source code in pytorch_optimizer/optimizer/utils/model.py
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
def reg_noise(
    network1: nn.Module,
    network2: nn.Module,
    num_data: int,
    lr: float,
    eta: float = 8e-3,
    temperature: float = 1e-4,
) -> torch.Tensor:
    """Compute the Entropy-MCMC coupling and noise term for two networks.

    Usage example and detailed implementation can be found at:
    https://github.com/lblaoke/EMCMC/blob/master/exp/cifar10_emcmc.py

    Args:
        network1: First neural network.
        network2: Second neural network.
        num_data: Number of training data points.
        lr: Learning rate.
        eta: Eta parameter controlling auxiliary guiding variable.
        temperature: Temperature parameter for sampling.

    """
    reg_coef: float = 0.5 / (eta * num_data)
    noise_coef: float = math.sqrt(2.0 / lr / num_data * temperature)

    loss = torch.tensor(0.0, device=next(network1.parameters()).device)

    for param1, param2 in zip(network1.parameters(), network2.parameters()):
        reg = (param1 - param2).pow_(2).mul_(reg_coef).sum()

        noise = param1 * torch.randn_like(param1)
        noise.add_(param2 * torch.randn_like(param2))

        loss.add_(reg - noise.mul_(noise_coef).sum())

    return loss

copy_stochastic(target, source)

Copy float32 values to bfloat16 with stochastic rounding.

reference: https://github.com/pytorch/pytorch/issues/120376#issuecomment-1974828905

Parameters:

Name Type Description Default
target Tensor

A tensor in bfloat16 format to copy to.

required
source Tensor

A tensor in float32 format to copy from.

required
Source code in pytorch_optimizer/optimizer/utils/precision.py
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
@torch.no_grad()
def copy_stochastic(target: torch.Tensor, source: torch.Tensor) -> None:
    """Copy float32 values to bfloat16 with stochastic rounding.

    reference: https://github.com/pytorch/pytorch/issues/120376#issuecomment-1974828905

    Args:
        target: A tensor in bfloat16 format to copy to.
        source: A tensor in float32 format to copy from.

    """
    result = torch.randint_like(
        source,
        dtype=torch.int32,
        low=0,
        high=1 << 16,
    )

    result.add_(source.view(dtype=torch.int32))

    result.bitwise_and_(-65536)

    target.copy_(result.view(dtype=torch.float32))

batched_power_iteration(matrix, num_iters=10, num_vectors=16)

Estimate the largest eigenvalue of each positive semidefinite matrix in a batch.

Parameters:

Name Type Description Default
matrix Tensor

Batch of symmetric positive semidefinite matrices with shape (batch_size, size, size).

required
num_iters int

Number of power iterations.

10
num_vectors int

Number of parallel starting vectors per matrix.

16

Returns:

Type Description
Tensor

torch.Tensor: Largest Rayleigh quotient per matrix, with shape (batch_size, 1, 1) and the input data type.

Source code in pytorch_optimizer/optimizer/utils/matrix.py
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
@torch.no_grad()
def batched_power_iteration(matrix: torch.Tensor, num_iters: int = 10, num_vectors: int = 16) -> torch.Tensor:
    """Estimate the largest eigenvalue of each positive semidefinite matrix in a batch.

    Args:
        matrix: Batch of symmetric positive semidefinite matrices with shape `(batch_size, size, size)`.
        num_iters: Number of power iterations.
        num_vectors: Number of parallel starting vectors per matrix.

    Returns:
        torch.Tensor: Largest Rayleigh quotient per matrix, with shape `(batch_size, 1, 1)` and the input data type.

    """
    vectors = torch.randn((*matrix.shape[:2], num_vectors), device=matrix.device, dtype=matrix.dtype)
    tiny = torch.finfo(vectors.dtype).tiny
    vectors.div_(torch.linalg.vector_norm(vectors, dim=1, keepdim=True).clamp_min_(tiny))

    product = torch.empty_like(vectors)
    for _ in range(num_iters):
        torch.bmm(matrix, vectors, out=product)
        torch.div(product, torch.linalg.vector_norm(product, dim=1, keepdim=True).clamp_min_(tiny), out=vectors)

    torch.bmm(matrix, vectors, out=product)

    return (vectors * product).sum(dim=1).amax(dim=1).view(-1, 1, 1)

compute_power_newton_db(matrix, scale, num_iters=10, inverse=True)

Compute batched square roots or inverse square roots with Newton-Denman-Beavers iterations.

Parameters:

Name Type Description Default
matrix Tensor

Batch of symmetric positive definite matrices with shape (batch_size, size, size).

required
scale Tensor

Positive normalization factors with shape (batch_size, 1, 1) that bound the largest eigenvalues.

required
num_iters int

Positive number of Newton-Denman-Beavers iterations.

10
inverse bool

Return inverse square roots when True, or square roots otherwise.

True

Returns:

Type Description
Tensor

torch.Tensor: Approximate matrix roots with the same shape and data type as matrix.

Source code in pytorch_optimizer/optimizer/utils/matrix.py
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
@torch.no_grad()
def compute_power_newton_db(
    matrix: torch.Tensor, scale: torch.Tensor, num_iters: int = 10, inverse: bool = True
) -> torch.Tensor:
    """Compute batched square roots or inverse square roots with Newton-Denman-Beavers iterations.

    Args:
        matrix: Batch of symmetric positive definite matrices with shape `(batch_size, size, size)`.
        scale: Positive normalization factors with shape `(batch_size, 1, 1)` that bound the largest eigenvalues.
        num_iters: Positive number of Newton-Denman-Beavers iterations.
        inverse: Return inverse square roots when `True`, or square roots otherwise.

    Returns:
        torch.Tensor: Approximate matrix roots with the same shape and data type as `matrix`.

    """
    y = matrix / scale
    correction = y.mul(-0.5)
    correction.diagonal(dim1=-2, dim2=-1).add_(1.5)
    z = correction.clone()

    if num_iters > 1 or not inverse:
        y = y @ correction

    scratch = torch.empty_like(y)
    for _ in range(1, num_iters - 1):
        torch.bmm(z, y, out=correction)
        correction.mul_(-0.5).diagonal(dim1=-2, dim2=-1).add_(1.5)

        torch.bmm(y, correction, out=scratch)
        y, scratch = scratch, y

        torch.bmm(correction, z, out=scratch)
        z, scratch = scratch, z

    if num_iters > 1:
        torch.bmm(z, y, out=correction)
        correction.mul_(-0.5).diagonal(dim1=-2, dim2=-1).add_(1.5)

        if inverse:
            torch.bmm(correction, z, out=scratch)
        else:
            torch.bmm(y, correction, out=scratch)

        result = scratch
    else:
        result = z if inverse else y

    return result.div_(scale.sqrt()) if inverse else result.mul_(scale.sqrt())