Aleksei Ustimenko
Initial HamiltonZero release
5ccb4fd
Raw
History Blame Contribute Delete
55.7 kB
# Copyright (c) 2026 Simulacra Research Inc.
# SPDX-License-Identifier: Apache-2.0
from __future__ import annotations
import math
import jax
import jax.numpy as jnp
import kfac_jax
from kfac_jax._src import utils as kfac_utils
from kfac_jax._src.curvature_blocks import utils as cb_utils
from kfac_jax._src.layers_and_loss_tags import LayerMetaData, layer_tag
STRUCTURAL_DENSE_TAG_VARIANT = "structural_repeated_dense"
STRUCTURAL_SCALE_SHIFT_TAG_VARIANT = "structural_scale_and_shift"
STRUCTURAL_STACKED_DENSE_TAG_VARIANT = "structural_stacked_repeated_dense"
STRUCTURAL_STACKED_SCALE_SHIFT_TAG_VARIANT = "structural_stacked_scale_and_shift"
STRUCTURAL_TRAILING_STACKED_SCALE_SHIFT_TAG_VARIANT = (
"structural_trailing_stacked_scale_and_shift"
)
def _optional_name_kw(name: str | None) -> dict[str, str]:
return {} if name is None else {"name": name}
def _validate_structural_registration(
x,
structural_mask,
*,
repeat_ndim: int,
feature_ndim: int,
) -> None:
if int(repeat_ndim) < 0:
raise ValueError(f"repeat_ndim must be non-negative, got {repeat_ndim}")
expected_mask_shape = x.shape if feature_ndim == 0 else x.shape[:-feature_ndim]
if tuple(structural_mask.shape) != tuple(expected_mask_shape):
raise ValueError(
"structural_mask must exactly cover the local repeat axes: "
f"mask={structural_mask.shape}, expected={expected_mask_shape}, "
f"x={x.shape}, feature_ndim={feature_ndim}"
)
def register_structural_dense(
y,
x,
structural_mask,
weight,
bias=None,
*,
scan_shared: bool,
repeat_ndim: int,
context_primal_reused_over_walkers: bool = False,
name: str | None = None,
):
_validate_structural_registration(
x,
structural_mask,
repeat_ndim=repeat_ndim,
feature_ndim=1,
)
args = (
(y, x, structural_mask, weight)
if bias is None
else (y, x, structural_mask, weight, bias)
)
return layer_tag.bind(
*args,
meta=LayerMetaData(
variant=STRUCTURAL_DENSE_TAG_VARIANT,
outputs_index=(0,),
inputs_index=(1, 2),
params_index=tuple(range(3, len(args))),
),
scan_shared=bool(scan_shared),
repeat_ndim=int(repeat_ndim),
context_primal_reused_over_walkers=bool(context_primal_reused_over_walkers),
**_optional_name_kw(name),
)
def register_structural_scale_and_shift(
y,
x,
structural_mask,
scale=None,
shift=None,
*,
scan_shared: bool,
repeat_ndim: int,
context_primal_reused_over_walkers: bool = False,
name: str | None = None,
):
params = tuple(value for value in (scale, shift) if value is not None)
if not params:
raise ValueError("At least one of scale and shift must be provided")
feature_ndim = params[0].ndim
if any(tuple(param.shape) != tuple(params[0].shape) for param in params[1:]):
raise ValueError("structural scale and shift shapes must match")
_validate_structural_registration(
x,
structural_mask,
repeat_ndim=repeat_ndim,
feature_ndim=feature_ndim,
)
args = (y, x, structural_mask, *params)
return layer_tag.bind(
*args,
meta=LayerMetaData(
variant=STRUCTURAL_SCALE_SHIFT_TAG_VARIANT,
outputs_index=(0,),
inputs_index=(1, 2),
params_index=tuple(range(3, len(args))),
),
has_scale=scale is not None,
has_shift=shift is not None,
scan_shared=bool(scan_shared),
repeat_ndim=int(repeat_ndim),
context_primal_reused_over_walkers=bool(context_primal_reused_over_walkers),
**_optional_name_kw(name),
)
def register_structural_trailing_stacked_scale_and_shift(
y,
x,
structural_mask,
scale,
*,
repeat_ndim: int,
context_primal_reused_over_walkers: bool = False,
name: str | None = None,
):
if scale.ndim != 2:
raise ValueError(
"trailing stacked scale/shift parameters must have shape [K,d]; "
f"got {scale.shape}"
)
_validate_structural_registration(
x,
structural_mask,
repeat_ndim=repeat_ndim,
feature_ndim=2,
)
args = (y, x, structural_mask, scale)
return layer_tag.bind(
*args,
meta=LayerMetaData(
variant=STRUCTURAL_TRAILING_STACKED_SCALE_SHIFT_TAG_VARIANT,
outputs_index=(0,),
inputs_index=(1, 2),
params_index=(3,),
),
has_scale=True,
has_shift=False,
scan_shared=False,
repeat_ndim=int(repeat_ndim),
context_primal_reused_over_walkers=bool(context_primal_reused_over_walkers),
**_optional_name_kw(name),
)
def _structural_tag_contract(layer_tag_eq):
params = layer_tag_eq.params
return (
bool(params["scan_shared"]),
int(params["repeat_ndim"]),
bool(params.get("context_primal_reused_over_walkers", False)),
)
def _align_structural_primal_and_mask(
x,
dy,
structural_mask,
*,
repeat_ndim: int,
feature_ndim: int,
context_primal_reused_over_walkers: bool,
):
structural_mask = jnp.asarray(structural_mask, dtype=bool)
x_leading = tuple(x.shape[:-feature_ndim]) if feature_ndim else tuple(x.shape)
dy_leading = tuple(dy.shape[:-feature_ndim]) if feature_ndim else tuple(dy.shape)
def _missing_walker_axis(source_leading, target_leading, *, what):
if source_leading == target_leading:
return None
if len(target_leading) != len(source_leading) + 1:
raise ValueError(
f"{what} supports exactly one missing walker sample axis: "
f"source={source_leading}, target={target_leading}"
)
insert_axis = len(source_leading) - int(repeat_ndim)
if insert_axis < 0 or (
source_leading[:insert_axis] != target_leading[:insert_axis]
or source_leading[insert_axis:] != target_leading[insert_axis + 1 :]
):
raise ValueError(
f"{what} walker axis must be the final logical-sample axis "
f"before the {repeat_ndim} repeat axes: "
f"source={source_leading}, target={target_leading}"
)
return insert_axis
x_insert_axis = _missing_walker_axis(
x_leading,
dy_leading,
what="context primal reuse",
)
if x_insert_axis is not None:
if not context_primal_reused_over_walkers:
raise ValueError(
"x/dy structural layouts differ without "
"context_primal_reused_over_walkers: "
f"x={x.shape}, dy={dy.shape}, mask={structural_mask.shape}"
)
x = jnp.expand_dims(x, axis=x_insert_axis)
x = jnp.broadcast_to(
x,
(*dy_leading, *x.shape[-feature_ndim:]) if feature_ndim else dy_leading,
)
structural_mask = align_structural_mask_to_leading(
structural_mask,
dy_leading,
repeat_ndim=repeat_ndim,
)
return x, dy, structural_mask
def align_structural_mask_to_leading(
structural_mask,
target_leading,
*,
repeat_ndim: int,
):
structural_mask = jnp.asarray(structural_mask, dtype=bool)
source_leading = tuple(structural_mask.shape)
target_leading = tuple(target_leading)
if source_leading == target_leading:
return structural_mask
if len(target_leading) != len(source_leading) + 1:
raise ValueError(
"structural mask supports exactly one missing walker sample "
f"axis: source={source_leading}, target={target_leading}"
)
insert_axis = len(source_leading) - int(repeat_ndim)
if insert_axis < 0 or (
source_leading[:insert_axis] != target_leading[:insert_axis]
or source_leading[insert_axis:] != target_leading[insert_axis + 1 :]
):
raise ValueError(
"structural mask walker axis must be the final logical-sample "
f"axis before the {repeat_ndim} repeat axes: "
f"source={source_leading}, target={target_leading}"
)
structural_mask = jnp.expand_dims(structural_mask, axis=insert_axis)
return jnp.broadcast_to(structural_mask, target_leading)
def structural_group_repeats(
value,
structural_mask,
*,
scan_shared: bool,
repeat_ndim: int,
feature_ndim: int,
):
feature_shape = tuple(value.shape[-feature_ndim:]) if feature_ndim else ()
leading_shape = (
tuple(value.shape[:-feature_ndim]) if feature_ndim else tuple(value.shape)
)
if tuple(structural_mask.shape) != leading_shape:
raise ValueError(
f"mask/value leading mismatch: {structural_mask.shape} vs {leading_shape}"
)
scan_ndim = 1 if scan_shared else 0
if len(leading_shape) < scan_ndim + int(repeat_ndim):
raise ValueError(
"not enough leading axes for structural layout: "
f"shape={value.shape}, scan_shared={scan_shared}, "
f"repeat_ndim={repeat_ndim}"
)
sample_end = len(leading_shape) - int(repeat_ndim)
sample_axes = tuple(range(scan_ndim, sample_end))
repeat_axes = ((0,) if scan_shared else ()) + tuple(
range(sample_end, len(leading_shape))
)
feature_axes = tuple(range(len(leading_shape), value.ndim))
permutation = (*sample_axes, *repeat_axes, *feature_axes)
mask_permutation = (*sample_axes, *repeat_axes)
value = jnp.transpose(value, permutation) if permutation else value
structural_mask = (
jnp.transpose(structural_mask, mask_permutation)
if mask_permutation
else structural_mask
)
logical_batch = int(math.prod(leading_shape[i] for i in sample_axes)) or 1
repeats = int(math.prod(leading_shape[i] for i in repeat_axes)) or 1
return (
value.reshape(logical_batch, repeats, *feature_shape),
structural_mask.reshape(logical_batch, repeats),
logical_batch,
repeats,
)
def _floor_matrix_avg_diag(mat, eps: float):
d = mat.shape[-1]
eps_arr = jnp.asarray(eps, dtype=mat.dtype)
avg_diag = jnp.trace(mat) / d
shift = jnp.maximum(eps_arr, eps_arr - avg_diag)
return mat + shift * jnp.eye(d, dtype=mat.dtype)
def _floor_diag_avg(vec, eps: float):
eps_arr = jnp.asarray(eps, dtype=vec.dtype)
avg_diag = jnp.mean(vec)
shift = jnp.maximum(eps_arr, eps_arr - avg_diag)
return vec + shift
def _iter_factor_update(raw_update, n_iter: int, eps: float, dtype):
del eps
if n_iter == 1:
return jnp.ones((1, 1), dtype=dtype)
return raw_update
def _iter_factor_for_inverse(raw_update, n_iter: int, eps: float, dtype):
return _floor_matrix_avg_diag(
_iter_factor_update(raw_update, n_iter, eps, dtype),
eps,
)
def _validate_approx_inverse_cache_request(
exact_powers_to_cache,
approx_powers_to_cache,
):
if exact_powers_to_cache:
raise NotImplementedError(
"Custom Kronecker blocks do not implement exact cached powers."
)
unsupported = set(approx_powers_to_cache) - {-1}
if unsupported:
raise NotImplementedError(
f"Unsupported approximate cached powers: {sorted(unsupported)}."
)
def _identity_factor(shape, dtype):
shape = tuple(shape)
if len(shape) == 1:
return jnp.ones(shape, dtype=dtype)
if len(shape) == 2 and shape[0] == shape[1]:
return jnp.eye(shape[0], dtype=dtype)
raise ValueError(f"Unsupported Kronecker factor shape: {shape}.")
def _init_factor_inverse_cache(
factor_shapes,
dtype,
exact_powers_to_cache,
approx_powers_to_cache,
cache_eigenvalues,
eigenvalue_count,
):
_validate_approx_inverse_cache_request(
exact_powers_to_cache,
approx_powers_to_cache,
)
cache = {}
if -1 in approx_powers_to_cache:
cache["-1"] = {
f"{i}_factor": _identity_factor(shape, dtype)
for i, shape in enumerate(factor_shapes)
}
if cache_eigenvalues:
cache["eigenvalues"] = jnp.zeros((eigenvalue_count,), dtype=dtype)
return cache
@kfac_utils.register_state_class
class _StackedRepeatedDenseState(kfac_jax.CurvatureBlock.State):
K_iter: kfac_utils.WeightedMovingAverage
A: kfac_utils.WeightedMovingAverage
G: kfac_utils.WeightedMovingAverage
average_repeats: kfac_utils.WeightedMovingAverage
class _StackedRepeatedDense(kfac_jax.CurvatureBlock):
State = _StackedRepeatedDenseState
_MATPOWER_EPSILON_FLOOR: float = 1e-6
@property
def n_iter(self) -> int:
return int(self.parameters_shapes[0][0])
@property
def in_dim(self) -> int:
wshape = tuple(self.parameters_shapes[0][1:])
if len(wshape) == 0:
return 1
if len(wshape) == 1:
return 1
return int(math.prod(wshape[:-1]))
@property
def out_dim(self) -> int:
wshape = tuple(self.parameters_shapes[0][1:])
if len(wshape) == 0:
return 1
return int(wshape[-1])
@property
def in_dim_aug(self) -> int:
return self.in_dim + (1 if self.number_of_parameters == 2 else 0)
def _init(
self,
rng,
exact_powers_to_cache,
approx_powers_to_cache,
cache_eigenvalues,
):
del rng
K = self.n_iter
cache = _init_factor_inverse_cache(
(
(K, K),
(self.in_dim_aug, self.in_dim_aug),
(self.out_dim, self.out_dim),
),
self.dtype,
exact_powers_to_cache,
approx_powers_to_cache,
cache_eigenvalues,
self.dim,
)
return self.State(
cache=cache,
K_iter=kfac_utils.WeightedMovingAverage(
value=jnp.eye(K, dtype=self.dtype),
weight=jnp.asarray(1.0, dtype=self.dtype),
),
A=kfac_utils.WeightedMovingAverage(
value=jnp.eye(self.in_dim_aug, dtype=self.dtype),
weight=jnp.asarray(1.0, dtype=self.dtype),
),
G=kfac_utils.WeightedMovingAverage(
value=jnp.eye(self.out_dim, dtype=self.dtype),
weight=jnp.asarray(1.0, dtype=self.dtype),
),
average_repeats=kfac_utils.WeightedMovingAverage(
value=jnp.ones((K,), dtype=self.dtype),
weight=jnp.asarray(1.0, dtype=self.dtype),
),
)
def sync(self, state, pmap_axis_name):
state = state.copy()
state.K_iter.sync(pmap_axis_name)
state.A.sync(pmap_axis_name)
state.G.sync(pmap_axis_name)
state.average_repeats.sync(pmap_axis_name)
return state
def _locate_iter_axis(self, arr_shape) -> int:
n_iter = self.n_iter
candidates = [i for i, s in enumerate(arr_shape) if s == n_iter]
if not candidates:
raise ValueError(
f"{type(self).__name__}: no axis of size n_iter={n_iter} "
f"in shape {arr_shape}. Hoist contract drifted. "
f"parameters_shapes={self.parameters_shapes!r}"
)
return 0 if 0 in candidates else candidates[0]
def _iter_axis_tensors(self, x, dy):
ax_x = self._locate_iter_axis(x.shape)
ax_dy = self._locate_iter_axis(dy.shape)
return jnp.moveaxis(x, ax_x, 0), jnp.moveaxis(dy, ax_dy, 0)
def state_dependent_scale(self, state):
repeats = jnp.mean(state.average_repeats.value)
return 1.0 / jnp.where(repeats > 0, repeats, 1.0)
def _multiply_matpower_unscaled(
self,
state,
vector,
identity_weight,
power,
exact_power,
use_cached,
):
if exact_power and power != 1:
raise NotImplementedError(
"StackedRepeatedDense implements approximate inverse powers only."
)
grad_aug = self._params_list_to_aug_array(vector)
if power == 1:
factors = (
_iter_factor_update(
state.K_iter.value,
self.n_iter,
self._MATPOWER_EPSILON_FLOOR,
self.dtype,
),
state.A.value,
state.G.value,
)
scale = self.state_dependent_scale(state) if use_cached else 1.0
new_grad_aug = kfac_utils.kronecker_product_axis_mul_v(
factors,
grad_aug,
axis_groups=[(0,), (1,), (2,)],
)
new_grad_aug = scale * new_grad_aug + identity_weight * grad_aug
elif power == -1:
if use_cached:
inv_factors = tuple(state.cache["-1"][f"{i}_factor"] for i in range(3))
else:
eps = self._MATPOWER_EPSILON_FLOOR
inv_factors = kfac_utils.pi_adjusted_kronecker_inverse(
_iter_factor_for_inverse(
state.K_iter.value,
self.n_iter,
eps,
self.dtype,
),
_floor_matrix_avg_diag(state.A.value, eps),
_floor_matrix_avg_diag(state.G.value, eps),
damping=identity_weight,
)
new_grad_aug = kfac_utils.kronecker_product_axis_mul_v(
inv_factors,
grad_aug,
axis_groups=[(0,), (1,), (2,)],
)
else:
raise NotImplementedError(
f"StackedRepeatedDense: power={power} not implemented "
f"(only ±1 supported)."
)
return self._aug_array_to_params_list(new_grad_aug)
def _params_list_to_aug_array(self, parameters_list):
W = parameters_list[0]
W_arr = W.reshape(self.n_iter, self.in_dim, self.out_dim)
if self.number_of_parameters == 2:
b = parameters_list[1]
b_aug = b.reshape(self.n_iter, 1, self.out_dim)
return jnp.concatenate([W_arr, b_aug], axis=1)
return W_arr
def _aug_array_to_params_list(self, arr):
W_shape = self.parameters_shapes[0]
W = arr[:, : self.in_dim, :].reshape(W_shape)
if self.number_of_parameters == 2:
b_shape = self.parameters_shapes[1]
b = arr[:, self.in_dim :, :].reshape(b_shape)
return [W, b]
return [W]
def _eigenvalues_unscaled(self, state, use_cached):
if use_cached:
return state.cache["eigenvalues"]
s_K, _ = kfac_utils.safe_psd_eigh(
_iter_factor_update(
state.K_iter.value,
self.n_iter,
self._MATPOWER_EPSILON_FLOOR,
self.dtype,
)
)
s_A, _ = kfac_utils.safe_psd_eigh(state.A.value)
s_G, _ = kfac_utils.safe_psd_eigh(state.G.value)
return jnp.einsum("k,a,o->kao", s_K, s_A, s_G).reshape(-1)
def _update_cache(
self,
state,
identity_weight,
exact_powers,
approx_powers,
eigenvalues,
):
_validate_approx_inverse_cache_request(exact_powers, approx_powers)
state = state.copy()
eps = self._MATPOWER_EPSILON_FLOOR
factors = (
_iter_factor_for_inverse(
state.K_iter.value,
self.n_iter,
eps,
self.dtype,
),
_floor_matrix_avg_diag(state.A.value, eps),
_floor_matrix_avg_diag(state.G.value, eps),
)
scale = self.state_dependent_scale(state)
if eigenvalues:
state.cache["eigenvalues"] = scale * self._eigenvalues_unscaled(
state, use_cached=False
)
if -1 in approx_powers:
inv_factors = kfac_utils.pi_adjusted_kronecker_inverse(
*factors,
damping=identity_weight,
)
factor_scale = jnp.power(scale, 1.0 / len(factors))
for i, inv_factor in enumerate(inv_factors):
state.cache["-1"][f"{i}_factor"] = inv_factor / factor_scale
return state
def _to_dense_unscaled(self, state):
F_KA = jnp.kron(
_iter_factor_update(
state.K_iter.value,
self.n_iter,
self._MATPOWER_EPSILON_FLOOR,
self.dtype,
),
state.A.value,
)
return jnp.kron(F_KA, state.G.value)
def _norm_unscaled(self, state, norm_type):
n_K = kfac_utils.psd_matrix_norm(
_iter_factor_update(
state.K_iter.value,
self.n_iter,
self._MATPOWER_EPSILON_FLOOR,
self.dtype,
),
norm_type=norm_type,
)
n_A = kfac_utils.psd_matrix_norm(
state.A.value,
norm_type=norm_type,
)
n_G = kfac_utils.psd_matrix_norm(
state.G.value,
norm_type=norm_type,
)
return n_K * n_A * n_G
STACKED_SCALE_SHIFT_TAG_VARIANT = "stacked_scale_and_shift"
@kfac_utils.register_state_class
class _StackedScaleAndShiftState(kfac_jax.CurvatureBlock.State):
K_iter_factors: tuple[kfac_utils.WeightedMovingAverage, ...]
D_shared_factors: tuple[kfac_utils.WeightedMovingAverage, ...]
class _StackedScaleAndShiftDiagonal(kfac_jax.CurvatureBlock):
State = _StackedScaleAndShiftState
_MATPOWER_EPSILON_FLOOR: float = 1e-6
@property
def n_iter(self) -> int:
return int(self.parameters_shapes[0][0])
@property
def _per_iter_shapes(self) -> tuple[tuple[int, ...], ...]:
return (tuple(self.parameters_shapes[0][1:]),)
@property
def _per_iter_d_flats(self) -> tuple[int, ...]:
shape = self._per_iter_shapes[0]
return (int(math.prod(shape)) if shape else 1,)
def _locate_iter_axis(self, arr_shape) -> int:
n_iter = self.n_iter
candidates = [i for i, s in enumerate(arr_shape) if s == n_iter]
if not candidates:
raise ValueError(
f"{type(self).__name__}: no axis of size n_iter={n_iter} "
f"in shape {arr_shape}."
)
return 0 if 0 in candidates else candidates[0]
def _iter_axis_tensors(self, x, dy):
ax_x = self._locate_iter_axis(x.shape)
ax_dy = self._locate_iter_axis(dy.shape)
return jnp.moveaxis(x, ax_x, 0), jnp.moveaxis(dy, ax_dy, 0)
def _init(
self,
rng,
exact_powers_to_cache,
approx_powers_to_cache,
cache_eigenvalues,
):
del rng
K = self.n_iter
d = self._per_iter_d_flats[0]
cache = _init_factor_inverse_cache(
((K, K), (d,)),
self.dtype,
exact_powers_to_cache,
approx_powers_to_cache,
cache_eigenvalues,
self.dim,
)
return self.State(
cache=cache,
K_iter_factors=(
kfac_utils.WeightedMovingAverage(
value=jnp.eye(K, dtype=self.dtype),
weight=jnp.asarray(1.0, dtype=self.dtype),
),
),
D_shared_factors=(
kfac_utils.WeightedMovingAverage(
value=jnp.ones((d,), dtype=self.dtype),
weight=jnp.asarray(1.0, dtype=self.dtype),
),
),
)
def sync(self, state, pmap_axis_name):
state = state.copy()
state.K_iter_factors[0].sync(pmap_axis_name)
state.D_shared_factors[0].sync(pmap_axis_name)
return state
@kfac_utils.auto_scope_method
def update_curvature_matrix_estimate(
self,
state,
estimation_data,
ema_old,
ema_new,
identity_weight,
batch_size,
):
del identity_weight, batch_size
state = state.copy()
[x] = estimation_data.primals.inputs
[dy] = estimation_data.tangents.outputs
x_iter, dy_iter = self._iter_axis_tensors(x, dy)
mask_iter = 1.0 - jnp.all(
dy_iter == 0.0,
axis=-1,
keepdims=True,
)
n_iter = self.n_iter
per_iter_shape = self._per_iter_shapes[0]
d_flat = self._per_iter_d_flats[0]
def _per_iter(arr_i):
return cb_utils.compatible_sum(
arr_i,
per_iter_shape,
skip_axes=[0],
)
d_grad = jax.vmap(_per_iter)(x_iter * dy_iter).reshape(
n_iter,
-1,
d_flat,
)
mask = jnp.any(
mask_iter.reshape(n_iter, mask_iter.shape[1], -1) > 0,
axis=-1,
).astype(self.dtype)
d_grad = d_grad * mask[..., None]
n_active = jnp.maximum(jnp.sum(mask), 1.0).astype(self.dtype)
D_update = jnp.einsum("kbi,kbi->i", d_grad, d_grad) / n_active
if n_iter == 1:
K_update = jnp.ones((1, 1), dtype=self.dtype)
else:
weighted = d_grad * jnp.sqrt(jnp.maximum(D_update, 0.0))[None, None, :]
numerator = jnp.einsum("kbi,lbi->kl", weighted, weighted)
per_iter_active = jnp.sum(mask, axis=-1)
active_norm = jnp.sqrt(
jnp.maximum(
per_iter_active[:, None] * per_iter_active[None, :],
1.0,
)
).astype(self.dtype)
D_frob2 = jnp.maximum(jnp.sum(D_update * D_update), 1e-12)
K_update = numerator / (active_norm * D_frob2)
K_update = _iter_factor_update(
0.5 * (K_update + K_update.T),
n_iter,
self._MATPOWER_EPSILON_FLOOR,
self.dtype,
)
state.K_iter_factors[0].update(K_update, ema_old, ema_new)
state.D_shared_factors[0].update(D_update, ema_old, ema_new)
return state
def _multiply_matpower_unscaled(
self,
state,
vector,
identity_weight,
power,
exact_power,
use_cached,
):
if exact_power and power != 1:
raise NotImplementedError(
"StackedScaleAndShiftDiagonal implements approximate "
"inverse powers only."
)
n_iter = self.n_iter
v = vector[0]
v_flat = v.reshape(n_iter, -1)
if power == -1 and use_cached:
K_iter_inv = state.cache["-1"]["0_factor"]
D_shared_inv = state.cache["-1"]["1_factor"]
Kv = jnp.einsum("kl,li->ki", K_iter_inv, v_flat)
result_flat = D_shared_inv[None, :] * Kv
elif power == 1:
K_factor = _iter_factor_update(
state.K_iter_factors[0].value,
n_iter,
self._MATPOWER_EPSILON_FLOOR,
self.dtype,
)
D_factor = state.D_shared_factors[0].value
Kv = jnp.einsum("kl,li->ki", K_factor, v_flat)
result_flat = D_factor[None, :] * Kv + identity_weight * v_flat
elif power == -1:
eps = self._MATPOWER_EPSILON_FLOOR
K_floored = _iter_factor_for_inverse(
state.K_iter_factors[0].value,
n_iter,
eps,
self.dtype,
)
D_floored = _floor_diag_avg(
state.D_shared_factors[0].value,
eps,
)
shrink = jnp.maximum(
1.0,
jnp.mean(D_floored) / identity_weight,
)
D_floored = D_floored / shrink
K_iter_inv, D_shared_inv = kfac_utils.pi_adjusted_kronecker_inverse(
K_floored,
D_floored,
damping=identity_weight,
)
Kv = jnp.einsum("kl,li->ki", K_iter_inv, v_flat)
result_flat = D_shared_inv[None, :] * Kv
else:
raise NotImplementedError(
f"StackedScaleAndShiftDiagonal: power={power} not "
f"implemented (only ±1 supported)."
)
return (result_flat.reshape(v.shape),)
def _eigenvalues_unscaled(self, state, use_cached):
if use_cached:
return state.cache["eigenvalues"]
s_K, _ = kfac_utils.safe_psd_eigh(
_iter_factor_update(
state.K_iter_factors[0].value,
self.n_iter,
self._MATPOWER_EPSILON_FLOOR,
self.dtype,
)
)
return jnp.einsum(
"k,i->ki",
s_K,
state.D_shared_factors[0].value,
).reshape(-1)
def _update_cache(
self,
state,
identity_weight,
exact_powers,
approx_powers,
eigenvalues,
):
_validate_approx_inverse_cache_request(exact_powers, approx_powers)
state = state.copy()
if eigenvalues:
state.cache["eigenvalues"] = self._eigenvalues_unscaled(
state,
use_cached=False,
)
if -1 in approx_powers:
eps = self._MATPOWER_EPSILON_FLOOR
K_floored = _iter_factor_for_inverse(
state.K_iter_factors[0].value,
self.n_iter,
eps,
self.dtype,
)
D_floored = _floor_diag_avg(
state.D_shared_factors[0].value,
eps,
)
shrink = jnp.maximum(
1.0,
jnp.mean(D_floored) / identity_weight,
)
D_floored = D_floored / shrink
K_inv, D_inv = kfac_utils.pi_adjusted_kronecker_inverse(
K_floored,
D_floored,
damping=identity_weight,
)
state.cache["-1"]["0_factor"] = K_inv
state.cache["-1"]["1_factor"] = D_inv
return state
def _to_dense_unscaled(self, state):
return jnp.kron(
_iter_factor_update(
state.K_iter_factors[0].value,
self.n_iter,
self._MATPOWER_EPSILON_FLOOR,
self.dtype,
),
jnp.diag(state.D_shared_factors[0].value),
)
def _norm_unscaled(self, state, norm_type):
if norm_type in ("trace", "avg_diag"):
component_norm = "trace"
elif norm_type in ("fro", "avg_fro"):
component_norm = "fro"
else:
component_norm = norm_type
K = _iter_factor_update(
state.K_iter_factors[0].value,
self.n_iter,
self._MATPOWER_EPSILON_FLOOR,
self.dtype,
)
D = state.D_shared_factors[0].value
if component_norm == "trace":
norm = jnp.trace(K) * jnp.sum(D)
elif component_norm == "fro":
norm = jnp.linalg.norm(K) * jnp.linalg.norm(D)
elif component_norm == "2_norm":
norm = jnp.max(jnp.linalg.eigvalsh(K)) * jnp.max(D)
elif component_norm == "1_norm":
norm = jnp.max(jnp.sum(jnp.abs(K), axis=0)) * jnp.max(jnp.abs(D))
elif component_norm == "one_over_dim":
norm = jnp.asarray(1.0, dtype=self.dtype)
else:
raise NotImplementedError(
f"Kronecker norm {norm_type!r} is not needed by KFAC stats"
)
total_dim = self.n_iter * self._per_iter_d_flats[0]
if norm_type == "trace":
return norm
if norm_type == "avg_diag":
return norm / total_dim
if norm_type == "one_over_dim":
return jnp.asarray(1.0 / total_dim, dtype=self.dtype)
if norm_type in ("2_norm", "1_norm"):
return norm
if norm_type in ("fro", "avg_fro"):
return norm if norm_type == "fro" else norm / jnp.sqrt(total_dim)
raise NotImplementedError(
f"direct-sum norm {norm_type!r} is not needed by KFAC stats"
)
class _ScaleAndShiftDiagonal(kfac_jax.ScaleAndShiftDiagonal):
@kfac_utils.auto_scope_method
def update_curvature_matrix_estimate(
self,
state,
estimation_data,
ema_old,
ema_new,
identity_weight,
batch_size,
):
del identity_weight, batch_size
state = state.copy()
[x] = estimation_data.primals.inputs
[dy] = estimation_data.tangents.outputs
mask = 1.0 - jnp.all(dy == 0.0, axis=-1, keepdims=True)
x_masked = x * mask
n_active = jnp.maximum(jnp.sum(mask), 1.0).astype(x.dtype)
if self.has_scale:
scale_shape = estimation_data.primals.params[0].shape
n_param_dims = len(scale_shape)
x_flat = x_masked.reshape(
(-1,) + tuple(x_masked.shape[-n_param_dims:]) if n_param_dims else (-1,)
)
dy_flat = dy.reshape(
(-1,) + tuple(dy.shape[-n_param_dims:]) if n_param_dims else (-1,)
)
d_scale = cb_utils.compatible_sum(
x_flat * dy_flat,
scale_shape,
skip_axes=[0],
)
scale_diag_update = (
jnp.sum(
d_scale * d_scale,
axis=0,
keepdims=d_scale.ndim == len(scale_shape),
)
/ n_active
)
state.diagonal_factors[0].update(
scale_diag_update,
ema_old,
ema_new,
)
if self.has_shift:
shift_shape = estimation_data.primals.params[-1].shape
n_param_dims = len(shift_shape)
dy_flat = dy.reshape(
(-1,) + tuple(dy.shape[-n_param_dims:]) if n_param_dims else (-1,)
)
d_shift = cb_utils.compatible_sum(
dy_flat,
shift_shape,
skip_axes=[0],
)
shift_diag_update = (
jnp.sum(
d_shift * d_shift,
axis=0,
keepdims=d_shift.ndim == len(shift_shape),
)
/ n_active
)
state.diagonal_factors[-1].update(
shift_diag_update,
ema_old,
ema_new,
)
return state
def _norm_unscaled(self, state, norm_type):
diagonal = jnp.concatenate(
[factor.value.flatten() for factor in state.diagonal_factors],
axis=0,
)
return kfac_utils.psd_matrix_norm(
diagonal,
norm_type=norm_type,
)
def _multiply_matpower_unscaled(
self,
state,
vector,
identity_weight,
power,
exact_power,
use_cached,
):
scale = self.state_dependent_scale(state) if use_cached else 1.0
factors = []
for diagonal_factor in state.diagonal_factors:
value = scale * diagonal_factor.value
shrink = jnp.maximum(1.0, jnp.mean(value) / identity_weight)
factors.append(value / shrink + identity_weight)
assert len(factors) == len(vector)
if power == 1:
return tuple(factor * value for factor, value in zip(factors, vector))
elif power == -1:
return tuple(value / factor for factor, value in zip(factors, vector))
return tuple(
jnp.power(factor, power) * value for factor, value in zip(factors, vector)
)
class StructuralRepeatedDenseKroneckerFactored(
kfac_jax.RepeatedDenseKroneckerFactored,
):
def state_dependent_scale(self, state):
repeats = state.average_repeats.value
return 1.0 / jnp.where(repeats > 0, repeats, 1.0)
@kfac_utils.auto_scope_method
def update_curvature_matrix_estimate(
self,
state,
estimation_data,
ema_old,
ema_new,
identity_weight,
batch_size,
):
del identity_weight
state = state.copy()
x, structural_mask = estimation_data.primals.inputs
[dy] = estimation_data.tangents.outputs
scan_shared, repeat_ndim, context_reuse = _structural_tag_contract(
self._layer_tag_eq
)
try:
x, dy, structural_mask = _align_structural_primal_and_mask(
x,
dy,
structural_mask,
repeat_ndim=repeat_ndim,
feature_ndim=1,
context_primal_reused_over_walkers=context_reuse,
)
except ValueError as error:
meta = self._layer_tag_eq.params.get("meta")
raise ValueError(
f"{error}; structural dense tag="
f"{getattr(meta, 'name', None)!r}, scan_shared={scan_shared}, "
f"repeat_ndim={repeat_ndim}, context_reuse={context_reuse}, "
f"x={x.shape}, dy={dy.shape}, mask={structural_mask.shape}"
) from error
xg, mg, logical_batch, _ = structural_group_repeats(
x,
structural_mask,
scan_shared=scan_shared,
repeat_ndim=repeat_ndim,
feature_ndim=1,
)
dyg, _, dy_batch, _ = structural_group_repeats(
dy,
structural_mask,
scan_shared=scan_shared,
repeat_ndim=repeat_ndim,
feature_ndim=1,
)
if logical_batch != dy_batch:
raise ValueError(
f"x/dy logical batch mismatch: {logical_batch} vs {dy_batch}"
)
mask = mg.astype(xg.dtype)[..., None]
xg = xg * mask
dyg = dyg * mask.astype(dyg.dtype)
x_flat = xg.reshape((-1, xg.shape[-1]))
dy_flat = dyg.reshape((-1, dyg.shape[-1]))
if self.number_of_parameters == 2:
x_flat = jnp.concatenate(
[x_flat, mask.reshape((-1, 1))],
axis=-1,
)
logical_divisor = jnp.asarray(logical_batch, dtype=x_flat.dtype)
global_divisor = jnp.asarray(batch_size, dtype=dy_flat.dtype)
input_stats = jnp.einsum("ai,aj->ij", x_flat, x_flat) / logical_divisor
output_stats = jnp.einsum("ao,ap->op", dy_flat, dy_flat) / global_divisor
average_repeats = jnp.sum(mask) / logical_divisor
state.factors[0].update(input_stats, ema_old, ema_new)
state.factors[1].update(output_stats, ema_old, ema_new)
state.average_repeats.update(
average_repeats,
ema_old,
ema_new,
)
return state
class StructuralScaleAndShiftDiagonal(_ScaleAndShiftDiagonal):
@kfac_utils.auto_scope_method
def update_curvature_matrix_estimate(
self,
state,
estimation_data,
ema_old,
ema_new,
identity_weight,
batch_size,
):
del identity_weight
state = state.copy()
x, structural_mask = estimation_data.primals.inputs
[dy] = estimation_data.tangents.outputs
scan_shared, repeat_ndim, context_reuse = _structural_tag_contract(
self._layer_tag_eq
)
reference_param = estimation_data.primals.params[0]
feature_ndim = reference_param.ndim
try:
x, dy, structural_mask = _align_structural_primal_and_mask(
x,
dy,
structural_mask,
repeat_ndim=repeat_ndim,
feature_ndim=feature_ndim,
context_primal_reused_over_walkers=context_reuse,
)
except ValueError as error:
meta = self._layer_tag_eq.params.get("meta")
raise ValueError(
f"{error}; structural scale/shift tag="
f"{getattr(meta, 'name', None)!r}, scan_shared={scan_shared}, "
f"repeat_ndim={repeat_ndim}, context_reuse={context_reuse}, "
f"x={x.shape}, dy={dy.shape}, mask={structural_mask.shape}"
) from error
xg, mg, logical_batch, _ = structural_group_repeats(
x,
structural_mask,
scan_shared=scan_shared,
repeat_ndim=repeat_ndim,
feature_ndim=feature_ndim,
)
dyg, _, dy_batch, _ = structural_group_repeats(
dy,
structural_mask,
scan_shared=scan_shared,
repeat_ndim=repeat_ndim,
feature_ndim=feature_ndim,
)
if logical_batch != dy_batch:
raise ValueError(
f"x/dy logical batch mismatch: {logical_batch} vs {dy_batch}"
)
mask = mg.astype(dyg.dtype)
mask = mask.reshape((*mask.shape, *(1,) * feature_ndim))
xg = xg * mask.astype(xg.dtype)
dyg = dyg * mask
divisor = jnp.asarray(batch_size, dtype=dyg.dtype)
param_index = 0
if self.has_scale:
d_scale = jnp.sum(xg * dyg, axis=1)
scale_update = jnp.sum(d_scale * d_scale, axis=0) / divisor
state.diagonal_factors[param_index].update(
scale_update,
ema_old,
ema_new,
)
param_index += 1
if self.has_shift:
d_shift = jnp.sum(dyg, axis=1)
shift_update = jnp.sum(d_shift * d_shift, axis=0) / divisor
state.diagonal_factors[param_index].update(
shift_update,
ema_old,
ema_new,
)
return state
class StructuralStackedRepeatedDense(_StackedRepeatedDense):
def state_dependent_scale(self, state):
repeats = jnp.mean(state.average_repeats.value)
return 1.0 / jnp.where(repeats > 0, repeats, 1.0)
@kfac_utils.auto_scope_method
def update_curvature_matrix_estimate(
self,
state,
estimation_data,
ema_old,
ema_new,
identity_weight,
batch_size,
):
del identity_weight
state = state.copy()
x, structural_mask = estimation_data.primals.inputs
[dy] = estimation_data.tangents.outputs
scan_shared, repeat_ndim, context_reuse = _structural_tag_contract(
self._layer_tag_eq
)
x, dy, structural_mask = _align_structural_primal_and_mask(
x,
dy,
structural_mask,
repeat_ndim=repeat_ndim,
feature_ndim=1,
context_primal_reused_over_walkers=context_reuse,
)
ax_x = self._locate_iter_axis(x.shape)
ax_dy = self._locate_iter_axis(dy.shape)
ax_mask = self._locate_iter_axis(structural_mask.shape)
x_iter = jnp.moveaxis(x, ax_x, 0)
dy_iter = jnp.moveaxis(dy, ax_dy, 0)
mask_iter = jnp.moveaxis(structural_mask, ax_mask, 0)
x_groups = []
dy_groups = []
mask_groups = []
logical_batch = None
for k in range(self.n_iter):
xg, mg, B, _ = structural_group_repeats(
x_iter[k],
mask_iter[k],
scan_shared=scan_shared,
repeat_ndim=repeat_ndim,
feature_ndim=1,
)
dyg, _, B_dy, _ = structural_group_repeats(
dy_iter[k],
mask_iter[k],
scan_shared=scan_shared,
repeat_ndim=repeat_ndim,
feature_ndim=1,
)
if B != B_dy or (logical_batch is not None and B != logical_batch):
raise ValueError("stacked structural logical batches differ")
logical_batch = B
x_groups.append(xg)
dy_groups.append(dyg)
mask_groups.append(mg)
x_group = jnp.stack(x_groups, axis=0)
dy_group = jnp.stack(dy_groups, axis=0)
mask_group = jnp.stack(mask_groups, axis=0).astype(x_group.dtype)
x_group = x_group * mask_group[..., None]
dy_group = dy_group * mask_group[..., None].astype(dy_group.dtype)
if self.number_of_parameters == 2:
x_group = jnp.concatenate(
[x_group, mask_group[..., None]],
axis=-1,
)
K = self.n_iter
logical_batch = int(logical_batch or 1)
logical_divisor = jnp.asarray(K * logical_batch, dtype=self.dtype)
global_divisor = jnp.asarray(K * batch_size, dtype=self.dtype)
x_flat = x_group.reshape(K, -1, x_group.shape[-1])
dy_flat = dy_group.reshape(K, -1, dy_group.shape[-1])
A_update = jnp.einsum("kbi,kbj->ij", x_flat, x_flat) / logical_divisor
G_update = jnp.einsum("kbo,kbp->op", dy_flat, dy_flat) / global_divisor
per_iter_active = jnp.sum(mask_group, axis=(1, 2))
if K == 1:
K_iter_update = jnp.ones((1, 1), dtype=self.dtype)
else:
if context_reuse:
A_projection = state.A.value
G_projection = state.G.value
else:
A_projection = A_update
G_projection = G_update
xA = jnp.einsum("kbi,ij->kbj", x_flat, A_projection)
dyG = jnp.einsum("kbo,op->kbp", dy_flat, G_projection)
numerator = jnp.einsum(
"klb,klb->kl",
jnp.einsum("kbi,lbi->klb", xA, x_flat),
jnp.einsum("kbo,lbo->klb", dyG, dy_flat),
)
mean_repeats = jnp.mean(per_iter_active) / jnp.asarray(
logical_batch, self.dtype
)
denominator = (
jnp.asarray(batch_size, self.dtype)
* jnp.maximum(
jnp.sum(A_projection * A_projection),
1e-12,
)
* jnp.maximum(
jnp.sum(G_projection * G_projection),
1e-12,
)
)
K_iter_update = mean_repeats * numerator / denominator
K_iter_update = _iter_factor_update(
0.5 * (K_iter_update + K_iter_update.T),
K,
self._MATPOWER_EPSILON_FLOOR,
self.dtype,
)
state.K_iter.update(K_iter_update, ema_old, ema_new)
state.A.update(A_update, ema_old, ema_new)
state.G.update(G_update, ema_old, ema_new)
state.average_repeats.update(
per_iter_active / jnp.asarray(logical_batch, self.dtype),
ema_old,
ema_new,
)
return state
class StructuralStackedScaleAndShiftDiagonal(_StackedScaleAndShiftDiagonal):
def _structural_iter_axis(self, shape) -> int:
if shape and int(shape[0]) == self.n_iter:
return 0
return self._locate_iter_axis(shape)
@kfac_utils.auto_scope_method
def update_curvature_matrix_estimate(
self,
state,
estimation_data,
ema_old,
ema_new,
identity_weight,
batch_size,
):
del identity_weight
state = state.copy()
x, structural_mask = estimation_data.primals.inputs
[dy] = estimation_data.tangents.outputs
scan_shared, repeat_ndim, context_reuse = _structural_tag_contract(
self._layer_tag_eq
)
feature_ndim = len(self._per_iter_shapes[0])
x, dy, structural_mask = _align_structural_primal_and_mask(
x,
dy,
structural_mask,
repeat_ndim=repeat_ndim,
feature_ndim=feature_ndim,
context_primal_reused_over_walkers=context_reuse,
)
x_iter = jnp.moveaxis(x, self._structural_iter_axis(x.shape), 0)
dy_iter = jnp.moveaxis(dy, self._structural_iter_axis(dy.shape), 0)
mask_iter = jnp.moveaxis(
structural_mask,
self._structural_iter_axis(structural_mask.shape),
0,
)
self._update_structural_scale(
state,
x_iter,
dy_iter,
mask_iter,
self._per_iter_shapes[0],
scan_shared,
repeat_ndim,
context_reuse,
batch_size,
ema_old,
ema_new,
)
return state
def _update_structural_scale(
self,
state,
x_iter,
dy_iter,
mask_iter,
per_iter_shape,
scan_shared,
repeat_ndim,
context_reuse,
batch_size,
ema_old,
ema_new,
):
K = self.n_iter
feature_ndim = len(per_iter_shape)
grads = []
logical_batch = None
for k in range(K):
xg, mg, B, _ = structural_group_repeats(
x_iter[k],
mask_iter[k],
scan_shared=scan_shared,
repeat_ndim=repeat_ndim,
feature_ndim=feature_ndim,
)
dyg, _, B_dy, _ = structural_group_repeats(
dy_iter[k],
mask_iter[k],
scan_shared=scan_shared,
repeat_ndim=repeat_ndim,
feature_ndim=feature_ndim,
)
if B != B_dy or (logical_batch is not None and B != logical_batch):
raise ValueError("stacked scale logical batches differ")
logical_batch = B
mask = mg.astype(dyg.dtype).reshape((*mg.shape, *(1,) * feature_ndim))
row_grad = xg * dyg * mask
grads.append(jnp.sum(row_grad, axis=1).reshape(B, -1))
grad = jnp.stack(grads, axis=0)
logical_batch = int(logical_batch or 1)
D_update = jnp.einsum("kbi,kbi->i", grad, grad) / jnp.asarray(
K * batch_size,
self.dtype,
)
if K == 1:
K_update = jnp.ones((1, 1), dtype=self.dtype)
else:
D_projection = (
state.D_shared_factors[0].value if context_reuse else D_update
)
weighted = grad * jnp.sqrt(jnp.maximum(D_projection, 0.0))[None, None, :]
numerator = jnp.einsum("kbi,lbi->kl", weighted, weighted)
denom = jnp.asarray(batch_size, self.dtype) * jnp.maximum(
jnp.sum(D_projection * D_projection),
1e-12,
)
K_update = _iter_factor_update(
0.5 * (numerator / denom + (numerator / denom).T),
K,
self._MATPOWER_EPSILON_FLOOR,
self.dtype,
)
state.K_iter_factors[0].update(K_update, ema_old, ema_new)
state.D_shared_factors[0].update(D_update, ema_old, ema_new)
class StructuralTrailingStackedScaleAndShiftDiagonal(
StructuralStackedScaleAndShiftDiagonal,
):
@kfac_utils.auto_scope_method
def update_curvature_matrix_estimate(
self,
state,
estimation_data,
ema_old,
ema_new,
identity_weight,
batch_size,
):
del identity_weight
state = state.copy()
x, structural_mask = estimation_data.primals.inputs
[dy] = estimation_data.tangents.outputs
_, repeat_ndim, context_reuse = _structural_tag_contract(self._layer_tag_eq)
x, dy, structural_mask = _align_structural_primal_and_mask(
x,
dy,
structural_mask,
repeat_ndim=repeat_ndim,
feature_ndim=2,
context_primal_reused_over_walkers=context_reuse,
)
K = self.n_iter
if int(x.shape[-2]) != K or int(dy.shape[-2]) != K:
raise ValueError(
f"{type(self).__name__}: expected trailing K={K} axis, "
f"got x={x.shape}, dy={dy.shape}"
)
x_iter = jnp.moveaxis(x, -2, 0)
dy_iter = jnp.moveaxis(dy, -2, 0)
mask_with_groups = jnp.broadcast_to(
structural_mask[..., None],
x.shape[:-1],
)
mask_iter = jnp.moveaxis(mask_with_groups, -1, 0)
self._update_structural_scale(
state,
x_iter,
dy_iter,
mask_iter,
self._per_iter_shapes[0],
False,
repeat_ndim,
context_reuse,
batch_size,
ema_old,
ema_new,
)
return state
class _DenseBlock(kfac_jax.DenseTwoKroneckerFactored):
def update_curvature_matrix_estimate(
self,
state,
estimation_data,
ema_old,
ema_new,
identity_weight,
batch_size,
):
del identity_weight
state = state.copy()
[x] = estimation_data.primals.inputs
[dy] = estimation_data.tangents.outputs
if not kfac_jax.utils.first_dim_is_size(batch_size, x, dy):
x, dy = (
jnp.tile(a[None], (batch_size, *(1 for _ in a.shape))).reshape(
(-1, a.shape[-1])
)
for a in (x, dy)
)
batch_size = x.size // x.shape[-1]
assert kfac_jax.utils.first_dim_is_size(batch_size, x, dy)
mask = 1.0 - jnp.all(dy == 0.0, axis=-1, keepdims=True)
x = x * mask
n_active = jnp.maximum(jnp.sum(mask), 1.0).astype(x.dtype)
x = x.reshape((-1, x.shape[-1]))
dy = dy.reshape((-1, dy.shape[-1]))
input_stats = jnp.einsum("ay,az->yz", x, x) / n_active
output_stats = jnp.einsum("ay,az->yz", dy, dy) / n_active
state.factors[0].update(input_stats, ema_old, ema_new)
state.factors[1].update(output_stats, ema_old, ema_new)
return state
kfac_jax.set_default_tag_to_block_ctor("dense", _DenseBlock)
kfac_jax.set_default_tag_to_block_ctor(
"scale_and_shift",
_ScaleAndShiftDiagonal,
)
kfac_jax.set_default_tag_to_block_ctor(
STACKED_SCALE_SHIFT_TAG_VARIANT,
_StackedScaleAndShiftDiagonal,
)
kfac_jax.set_default_tag_to_block_ctor(
STRUCTURAL_DENSE_TAG_VARIANT,
StructuralRepeatedDenseKroneckerFactored,
)
kfac_jax.set_default_tag_to_block_ctor(
STRUCTURAL_SCALE_SHIFT_TAG_VARIANT,
StructuralScaleAndShiftDiagonal,
)
kfac_jax.set_default_tag_to_block_ctor(
STRUCTURAL_STACKED_DENSE_TAG_VARIANT,
StructuralStackedRepeatedDense,
)
kfac_jax.set_default_tag_to_block_ctor(
STRUCTURAL_STACKED_SCALE_SHIFT_TAG_VARIANT,
StructuralStackedScaleAndShiftDiagonal,
)
kfac_jax.set_default_tag_to_block_ctor(
STRUCTURAL_TRAILING_STACKED_SCALE_SHIFT_TAG_VARIANT,
StructuralTrailingStackedScaleAndShiftDiagonal,
)
def make_graph_patterns():
return ()
__all__ = [
"STRUCTURAL_DENSE_TAG_VARIANT",
"STRUCTURAL_SCALE_SHIFT_TAG_VARIANT",
"STRUCTURAL_STACKED_DENSE_TAG_VARIANT",
"STRUCTURAL_STACKED_SCALE_SHIFT_TAG_VARIANT",
"STRUCTURAL_TRAILING_STACKED_SCALE_SHIFT_TAG_VARIANT",
"StructuralRepeatedDenseKroneckerFactored",
"StructuralScaleAndShiftDiagonal",
"StructuralStackedRepeatedDense",
"StructuralStackedScaleAndShiftDiagonal",
"StructuralTrailingStackedScaleAndShiftDiagonal",
"make_graph_patterns",
"register_structural_dense",
"register_structural_scale_and_shift",
"register_structural_trailing_stacked_scale_and_shift",
"structural_group_repeats",
]