Skip to content

Commit d334a2f

Browse files
spcypptmeta-codesync[bot]
authored andcommitted
Fix unit test for rowwise adagrad with counter (#5908)
Summary: X-link: https://github.com/facebookresearch/FBGEMM/pull/2827 Pull Request resolved: #5908 Fix the reference implementation in `backward_optimizers_test.py` for `EXACT_ROWWISE_ADAGRAD` with `WeightDecayMode.COUNTER`. The numerical mismatch was masked by the `differing_executors` health check preventing the test from running. Two distinct discrepancies between the test reference and the kernel are fixed: 1. **Gradient used for the weight update (`L2`/`COWCLIP`).** The kernel computes momentum (`g_avg_square`) from the L2-modified gradient but uses the raw gradient for the weight update, applying weight decay separately via `exp_reg_correction`. The test was using the L2-modified gradient for both. Fixed by keeping a `raw_grad` copy and passing it to the weight-update helpers. 2. **Missing `lazy_multiplier` (`AdagradW`).** The kernel scales both `adjusted_multiplier` and `exp_reg_correction` by `(1 - wd * lr) ** (min(lazy_delta, iter - adjustment_iter) - 1)`, where `lazy_delta` depends on the *pre-update* `prev_iter` (the kernel reads `prev_iter` before overwriting it with the current iteration). The reference omitted this entirely and only had the post-update `prev_iter` from optimizer state. Fixed by capturing the pre-update `prev_iter` in the setup loop and implementing the `lazy_multiplier` in the `AdagradW` branch. Reviewed By: q10 Differential Revision: D108710672 fbshipit-source-id: 20d8b2d09a392d510ea21101a409537cce1aabee
1 parent 8453f6c commit d334a2f

1 file changed

Lines changed: 38 additions & 3 deletions

File tree

fbgemm_gpu/test/tbe/training/backward_optimizers_test.py

Lines changed: 38 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -394,6 +394,11 @@ def execute_backward_optimizers_( # noqa C901
394394
OptimType.EXACT_ROWWISE_ADAGRAD,
395395
OptimType.EXACT_ADAGRAD,
396396
)
397+
# Capture the pre-update `prev_iter` per table. The counter kernel reads
398+
# `prev_iter` *before* overwriting it with the current iteration, so the
399+
# reference needs the initial value (not the post-update state) to
400+
# reconstruct the AdagradW `lazy_multiplier`.
401+
prev_iter_init: dict[int, torch.Tensor] = {}
397402
if exact_adagrad:
398403
split_optimizer_states = cc.split_optimizer_states()
399404
rowwise = optimizer == OptimType.EXACT_ROWWISE_ADAGRAD
@@ -405,6 +410,7 @@ def execute_backward_optimizers_( # noqa C901
405410
m1, prev_iter, row_counter = split_optimizer_states[t]
406411
prev_iter.fill_(100.0 * t)
407412
row_counter.fill_(10.0 * t)
413+
prev_iter_init[t] = prev_iter.detach().clone().cpu()
408414
else:
409415
(m1,) = split_optimizer_states[t]
410416
m1.fill_(1.0 * t)
@@ -491,6 +497,12 @@ def execute_backward_optimizers_( # noqa C901
491497
# coalescing and floating point non-associativity.
492498
# pyre-fixme[16]: `Optional` has no attribute `cpu`.
493499
dense_cpu_grad = bs[t].weight.grad.cpu().to_dense()
500+
# The kernel computes momentum (g_avg_square) from the
501+
# L2-modified gradient, but uses the raw gradient for the
502+
# weight update (applying weight decay via exp_reg_correction
503+
# instead). Keep a copy of the raw gradient for the weight
504+
# update in COUNTER/COWCLIP modes.
505+
raw_grad = dense_cpu_grad.clone()
494506
if rowwise:
495507
# We need to skip when using cpu because use_fbgemm (https://fburl.com/code/12131iub)
496508
# is true and the template code (https://fburl.com/code/1kctlup3) is not executed.
@@ -545,7 +557,7 @@ def execute_backward_optimizers_( # noqa C901
545557
elif weight_decay_mode == WeightDecayMode.COUNTER:
546558
max_counter = cc.max_counter.item()
547559
weights_ref = self._get_wts_from_counter_adagrad_using_counter(
548-
dense_cpu_grad,
560+
raw_grad,
549561
bs[t].weight.cpu(),
550562
denom,
551563
counter_based_regularization,
@@ -557,10 +569,11 @@ def execute_backward_optimizers_( # noqa C901
557569
eps,
558570
lr,
559571
weight_decay,
572+
prev_iter_init[t],
560573
)
561574
elif weight_decay_mode == WeightDecayMode.COWCLIP:
562575
weights_ref = self._get_wts_from_counter_adagrad_using_cowclip(
563-
dense_cpu_grad,
576+
raw_grad,
564577
bs[t].weight.cpu(),
565578
denom,
566579
cowclip_regularization,
@@ -839,7 +852,7 @@ def _get_grad_from_counter_adagrad(
839852
dense_cpu_grad += l2_wd * weight_decay * weights
840853
return dense_cpu_grad, row_counter, freq
841854

842-
def _get_wts_from_counter_adagrad_using_counter(
855+
def _get_wts_from_counter_adagrad_using_counter( # noqa C901
843856
self,
844857
dense_cpu_grad: torch.Tensor,
845858
weights: torch.Tensor,
@@ -852,6 +865,7 @@ def _get_wts_from_counter_adagrad_using_counter(
852865
eps: float,
853866
learning_rate: float,
854867
weight_decay: float,
868+
prev_iter: torch.Tensor,
855869
) -> torch.Tensor:
856870
counter_weight_decay_mode = (
857871
counter_based_regularization.counter_weight_decay_mode
@@ -942,6 +956,27 @@ def _get_wts_from_counter_adagrad_using_counter(
942956
1.0 - weight_decay * learning_rate,
943957
torch.Tensor([1.0]),
944958
)
959+
# The kernel applies a `lazy_multiplier` that compensates for
960+
# the weight-decay steps skipped since the row was last seen
961+
# (`prev_iter`), scaling both the adjusted multiplier and the
962+
# weight-decay correction by `(1 - wd * lr) ** (delta - 1)`.
963+
prev_iter = prev_iter.view(prev_iter.numel(), 1)
964+
base = 1.0 - weight_decay * learning_rate
965+
lazy_delta = torch.where(
966+
prev_iter == 0,
967+
torch.tensor([1.0]),
968+
iter_ - prev_iter,
969+
)
970+
lazy_multiplier = torch.pow(
971+
torch.tensor([base]),
972+
torch.minimum(
973+
lazy_delta,
974+
torch.tensor([float(iter_ - adjustment_iter)]),
975+
)
976+
- 1.0,
977+
)
978+
adjusted_multiplier = adjusted_multiplier * lazy_multiplier
979+
exp_reg_correction = exp_reg_correction * lazy_multiplier
945980

946981
weights = exp_reg_correction * weights - adjusted_multiplier * dense_cpu_grad
947982
return weights

0 commit comments

Comments
 (0)