Source code for tbp.monty.attention.decay
# Copyright 2026 Thousand Brains Project
#
# Copyright may exist in Contributors' modifications
# and/or contributions to the work.
#
# Use of this source code is governed by the MIT
# license that can be found in the LICENSE file or at
# https://opensource.org/licenses/MIT.
from __future__ import annotations
from typing import Protocol
import numpy as np
from tbp.monty.attention.voxel_grid import VoxelGrid
DEFAULT_LINEAR_WEIGHT_DECAY_RATE = 0.1
"""Default per-step decay toward zero for linear decay."""
[docs]class VoxelGridWeightDecay(Protocol):
def __call__(self, grid: VoxelGrid) -> None: ...
[docs]class NoopDecay(VoxelGridWeightDecay):
"""Leave every voxel weight untouched."""
def __call__(self, grid: VoxelGrid) -> None:
"""Leave the grid as it is.
Args:
grid: The grid to (not) decay.
"""
[docs]class LinearWeightDecay(VoxelGridWeightDecay):
"""Move each voxel weight toward zero by a fixed amount.
Decay is applied in place to the grid's weights. Weights that fall
within [-rate, +rate] get clamped to zero.
"""
_rate: float
[docs] def __init__(self, rate: float = DEFAULT_LINEAR_WEIGHT_DECAY_RATE) -> None:
"""Initialize the decay.
Args:
rate: How much a weight moves toward zero per step.
Raises:
ValueError: If the rate is negative.
"""
if rate < 0.0:
raise ValueError(f"Rate must be non-negative, got {rate}")
self._rate = rate
def __call__(self, grid: VoxelGrid) -> None:
"""Decay every weight in the grid by one step, in place.
Args:
grid: The grid to decay.
"""
if len(grid) == 0 or self._rate == 0.0:
return
data = grid.to_pandas()
weights = data["weight"].to_numpy()
weights.flags.writeable = True
to_step = np.abs(weights) > self._rate
stepped = weights[to_step] - self._rate * np.sign(weights[to_step])
weights[to_step] = stepped
weights[~to_step] = 0.0
data["weight"] = weights