diff options
| author | YurenHao0426 <Blackhao0426@gmail.com> | 2026-07-22 13:47:35 -0500 |
|---|---|---|
| committer | YurenHao0426 <Blackhao0426@gmail.com> | 2026-07-22 13:47:35 -0500 |
| commit | 1e4fbaf8e773509798387d19b806f6daf38f8b5c (patch) | |
| tree | 43d5fe00aa7e1252e884c35cf3ae5a102799f073 /sdil | |
| parent | 203e75987d02cdb8c317e3302e1f05c9a4beb0e3 (diff) | |
baseline: add residual response mirroring
Diffstat (limited to 'sdil')
| -rw-r--r-- | sdil/conv.py | 73 |
1 files changed, 73 insertions, 0 deletions
diff --git a/sdil/conv.py b/sdil/conv.py index cc373e8..b3d9235 100644 --- a/sdil/conv.py +++ b/sdil/conv.py @@ -771,6 +771,70 @@ def normalized_response_mirror_update(net, observations, eta=0.1): @torch.no_grad() +def normalized_residual_mirror_update(net, observations, eta=0.1): + """Local normalized LMS on child-response prediction residuals. + + Unlike estimate-then-average mirroring, the stochastic update vanishes for + every probe when Q exactly matches W. It therefore removes the stationary + estimator noise that compounds through a deep feedback chain. The update + still consumes only fixed probe/response observations and Q/R. + """ + if not isinstance(net, CIFARHierarchicalFAResNet): + raise TypeError("residual mirror update requires a hierarchical net") + if not 0.0 < eta <= 1.0: + raise ValueError("residual mirror eta must be in (0, 1]") + variance = float(observations.get("noise_variance", 0.0)) + conv = observations.get("conv") + if variance <= 0 or conv is None or len(conv) != len(net.Q): + raise ValueError("invalid residual mirror observations") + update_power = 0.0 + residual_power = 0.0 + response_power = 0.0 + parameters = 0 + response_units = 0 + for index in range(1, len(net.Q)): + probe, response = conv[index] + spec = net.layer_specs[index] + prediction = F.conv2d( + probe, net.Q[index], stride=spec.stride, padding=spec.padding) + residual = response - prediction + correlation = torch.nn.grad.conv2d_weight( + probe, net.Q[index].shape, residual, + stride=spec.stride, padding=spec.padding) + counts = torch.nn.grad.conv2d_weight( + torch.ones_like(probe), net.Q[index].shape, + torch.ones_like(response), stride=spec.stride, + padding=spec.padding) + update = correlation / (variance * counts.clamp_min(1.0)) + net.Q[index].add_(update, alpha=eta) + update_power += float(update.square().sum()) + residual_power += float(residual.square().sum()) + response_power += float(response.square().sum()) + parameters += update.numel() + response_units += response.numel() + readout_probe, readout_response = observations["readout"] + readout_prediction = -(readout_probe @ net.R_out) + readout_residual = readout_response - readout_prediction + readout_update = -(readout_probe.t() @ readout_residual) / ( + variance * readout_probe.shape[0]) + net.R_out.add_(readout_update, alpha=eta) + update_power += float(readout_update.square().sum()) + residual_power += float(readout_residual.square().sum()) + response_power += float(readout_response.square().sum()) + parameters += readout_update.numel() + response_units += readout_response.numel() + return { + "mirror_update_rms": math.sqrt(update_power / parameters), + "mirror_response_residual_rms": math.sqrt( + residual_power / response_units), + "mirror_response_residual_fraction": math.sqrt( + residual_power / max(response_power, 1e-300)), + "conv_batch_size": int(observations["conv_batch_size"]), + "readout_batch_size": int(observations["readout_batch_size"]), + } + + +@torch.no_grad() def normalized_response_mirror_step( net, batch_size=1, noise_std=1.0, eta=0.1, generator=None): observations = hierarchical_mirror_observations( @@ -781,6 +845,15 @@ def normalized_response_mirror_step( return metrics +@torch.no_grad() +def normalized_residual_mirror_step( + net, batch_size=1, noise_std=1.0, eta=0.1, generator=None): + observations = hierarchical_mirror_observations( + net, batch_size=batch_size, noise_std=noise_std, + generator=generator) + return normalized_residual_mirror_update(net, observations, eta=eta) + + def conv_hierarchical_step(net, x, y, config): """One hierarchical-FA update using no reverse-mode graph or weight transport.""" config.validate() |
