|
| 1 | +# SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved. |
| 2 | +# SPDX-License-Identifier: Apache-2.0 |
| 3 | +# |
| 4 | +# Licensed under the Apache License, Version 2.0 (the "License"); |
| 5 | +# you may not use this file except in compliance with the License. |
| 6 | +# You may obtain a copy of the License at |
| 7 | +# |
| 8 | +# http://www.apache.org/licenses/LICENSE-2.0 |
| 9 | +# |
| 10 | +# Unless required by applicable law or agreed to in writing, software |
| 11 | +# distributed under the License is distributed on an "AS IS" BASIS, |
| 12 | +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 13 | +# See the License for the specific language governing permissions and |
| 14 | +# limitations under the License. |
| 15 | +import math |
| 16 | + |
| 17 | +import torch |
| 18 | +from torch import Tensor |
| 19 | + |
| 20 | +from emerging_optimizers import utils |
| 21 | +from emerging_optimizers.utils import FP32MatmulPrecT |
| 22 | + |
| 23 | + |
| 24 | +__all__ = ["scaled_cans_coupled_ns"] |
| 25 | + |
| 26 | +_CANS_COEFFS = ( |
| 27 | + (5.182503604966906, -5.178098480082684), |
| 28 | + (2.586120737395915, -0.6479542005271643), |
| 29 | + (2.567364126726186, -0.6454968804392178), |
| 30 | + (2.520560084348265, -0.6393528082067044), |
| 31 | + (2.410759275435182, -0.6248683598710716), |
| 32 | + (2.1883348130094173, -0.5952022073798908), |
| 33 | + (1.8595760874873613, -0.5504490972723968), |
| 34 | + (1.589020160467417, -0.5126569802066718), |
| 35 | + (1.5051653981684994, -0.5007377068751799), |
| 36 | + (1.5, -0.5), |
| 37 | +) |
| 38 | +_CANS_M = 16384.0 |
| 39 | +_CANS_Z_MAX = ( |
| 40 | + 1.0, |
| 41 | + 5.183, |
| 42 | + 13.403, |
| 43 | + 34.409, |
| 44 | + 86.731, |
| 45 | + 209.09, |
| 46 | + 457.55, |
| 47 | + 850.85, |
| 48 | + 1352.0, |
| 49 | + 2035.0, |
| 50 | + 3052.5, |
| 51 | +) |
| 52 | +_CANS_Y_MAX = (1.0, 1.297, 1.726, 1.473, 1.545, 1.415, 1.273, 1.148, 1.0, 1.0, 1.0) |
| 53 | +_CANS_P_MAX = (1.0, 3.98, 3.95, 3.88, 3.71, 3.32, 2.60, 1.73, 1.15, 1.008, 1.0) |
| 54 | +_CANS_S_Z = tuple(_CANS_M / value for value in _CANS_Z_MAX) |
| 55 | +_CANS_S_Y = tuple(_CANS_M / value for value in _CANS_Y_MAX) |
| 56 | +_CANS_S_P = tuple(_CANS_M / value for value in _CANS_P_MAX) |
| 57 | +_CANS_ALPHA_Y_0 = _CANS_COEFFS[0][1] * _CANS_S_Y[1] / (_CANS_S_Y[0] ** 2) |
| 58 | +_CANS_BETA_Y_0 = _CANS_COEFFS[0][0] * _CANS_S_Y[1] / _CANS_S_Y[0] |
| 59 | +_CANS_Z_SCALE_0 = _CANS_COEFFS[0][1] * _CANS_S_Z[1] / _CANS_S_Y[0] |
| 60 | +_CANS_Z_DIAG_ADD_0 = _CANS_COEFFS[0][0] * _CANS_S_Z[1] |
| 61 | +_CANS_ALPHA_P = tuple(_CANS_S_P[k] / (_CANS_S_Z[k] * _CANS_S_Y[k]) for k in range(1, len(_CANS_COEFFS))) |
| 62 | +_CANS_ALPHA_Y = tuple( |
| 63 | + _CANS_COEFFS[k][1] * _CANS_S_Y[k + 1] / (_CANS_S_Y[k] * _CANS_S_P[k]) for k in range(1, len(_CANS_COEFFS)) |
| 64 | +) |
| 65 | +_CANS_BETA_Y = tuple(_CANS_COEFFS[k][0] * _CANS_S_Y[k + 1] / _CANS_S_Y[k] for k in range(1, len(_CANS_COEFFS))) |
| 66 | +_CANS_ALPHA_Z_BASE = tuple( |
| 67 | + _CANS_COEFFS[k][1] * _CANS_S_Z[k + 1] / (_CANS_S_Z[k] * _CANS_S_P[k]) for k in range(1, len(_CANS_COEFFS)) |
| 68 | +) |
| 69 | +_CANS_BETA_Z_BASE = tuple(_CANS_COEFFS[k][0] * _CANS_S_Z[k + 1] / _CANS_S_Z[k] for k in range(1, len(_CANS_COEFFS))) |
| 70 | +_CANS_ALPHA_Z = _CANS_ALPHA_Z_BASE[:-1] + (_CANS_ALPHA_Z_BASE[-1] / _CANS_S_Z[-1],) |
| 71 | +_CANS_BETA_Z = _CANS_BETA_Z_BASE[:-1] + (_CANS_BETA_Z_BASE[-1] / _CANS_S_Z[-1],) |
| 72 | + |
| 73 | + |
| 74 | +def _estimate_max_eigenvalue(x: Tensor, eps: float) -> Tensor: |
| 75 | + n = x.size(-1) |
| 76 | + diag = x.diagonal(dim1=-2, dim2=-1) |
| 77 | + mean = diag.sum(dim=-1) / n |
| 78 | + sq_norm = torch.sum(x**2, dim=(-2, -1)) |
| 79 | + variance = torch.clamp((sq_norm / n) - (mean**2), min=0.0) |
| 80 | + ws_bound = mean + torch.sqrt(variance) * math.sqrt(n - 1) |
| 81 | + |
| 82 | + abs_x = torch.abs(x) |
| 83 | + row_sum = torch.sum(abs_x, dim=-1).clamp_min_(eps) |
| 84 | + minc_bound = torch.max(torch.einsum("...ij,...j->...i", abs_x, row_sum) / row_sum, dim=-1).values |
| 85 | + return torch.minimum(ws_bound, minc_bound) |
| 86 | + |
| 87 | + |
| 88 | +def scaled_cans_coupled_ns( |
| 89 | + x: Tensor, |
| 90 | + eps: float = 1e-12, |
| 91 | + fp32_matmul_prec: FP32MatmulPrecT = "highest", |
| 92 | +) -> Tensor: |
| 93 | + """Compute inverse square roots with scaled coupled CANS Newton-Schulz. |
| 94 | +
|
| 95 | + Args: |
| 96 | + x: A 2D symmetric positive-definite matrix or 3D batch of matrices. |
| 97 | + eps: Lower bound used when normalizing the matrices. |
| 98 | + fp32_matmul_prec: Precision used for FP32 matrix multiplications: ``"medium"`` for BF16, |
| 99 | + ``"high"`` for TF32, or ``"highest"`` for FP32. |
| 100 | +
|
| 101 | + Returns: |
| 102 | + The approximate inverse square root with the same shape as ``x``. |
| 103 | + """ |
| 104 | + if x.dim() not in (2, 3) or x.shape[-2] != x.shape[-1]: |
| 105 | + raise TypeError(f"x must be a square matrix or batch of square matrices, got shape {tuple(x.shape)}") |
| 106 | + if not x.is_cuda: |
| 107 | + raise TypeError("scaled_cans_coupled_ns only supports CUDA tensors") |
| 108 | + |
| 109 | + with utils.fp32_matmul_precision(fp32_matmul_prec): |
| 110 | + input_dtype = x.dtype |
| 111 | + x = x.float() |
| 112 | + max_eigval = (_estimate_max_eigenvalue(x, eps) * 1.01).clamp_min_(eps) |
| 113 | + max_eigval = max_eigval[..., None, None] |
| 114 | + cans_addmm = torch.addmm if x.dim() == 2 else torch.baddbmm |
| 115 | + y = x * (_CANS_S_Y[0] / max_eigval) |
| 116 | + |
| 117 | + z = y.mul(_CANS_Z_SCALE_0) |
| 118 | + z.diagonal(dim1=-2, dim2=-1).add_(_CANS_Z_DIAG_ADD_0) |
| 119 | + y = cans_addmm(y, y, y, beta=_CANS_BETA_Y_0, alpha=_CANS_ALPHA_Y_0) |
| 120 | + |
| 121 | + for k in range(8): |
| 122 | + p = cans_addmm(z, z, y, beta=0.0, alpha=_CANS_ALPHA_P[k]) |
| 123 | + y, z = ( |
| 124 | + cans_addmm(y, y, p, beta=_CANS_BETA_Y[k], alpha=_CANS_ALPHA_Y[k]), |
| 125 | + cans_addmm(z, p, z, beta=_CANS_BETA_Z[k], alpha=_CANS_ALPHA_Z[k]), |
| 126 | + ) |
| 127 | + |
| 128 | + p = cans_addmm(z, z, y, beta=0.0, alpha=_CANS_ALPHA_P[8]) |
| 129 | + inverse_root = cans_addmm(z, p, z, beta=_CANS_BETA_Z[8], alpha=_CANS_ALPHA_Z[8]) |
| 130 | + inverse_root.mul_(torch.rsqrt(max_eigval)) |
| 131 | + inverse_root = (inverse_root + inverse_root.mT) / 2.0 |
| 132 | + return inverse_root.to(input_dtype) |
0 commit comments