def delta_recurrent(Q, K, V, beta):
"""The delta rule in the error form: read, subtract, write, read out."""
S = Q.new_zeros(Q.shape[-1], V.shape[-1])
outputs = []
for t in range(Q.shape[0]):
error = V[t] - S.T @ K[t] # What the memory got wrong
S = S + beta[t] * K[t][:, None] * error[None, :]
outputs.append(S.T @ Q[t])
return torch.stack(outputs)
def delta_recurrent_matrix(Q, K, V, beta):
"""The same update as transition-then-write, for the family template."""
S = Q.new_zeros(Q.shape[-1], V.shape[-1])
I = torch.eye(Q.shape[-1], device=Q.device)
outputs = []
for t in range(Q.shape[0]):
S = (I - beta[t] * torch.outer(K[t], K[t])) @ S \
+ beta[t] * torch.outer(K[t], V[t])
outputs.append(S.T @ Q[t])
return torch.stack(outputs)
torch.manual_seed(0)
d_k, d_v, T = 64, 64, 512
Q, K, V = (torch.randn(T, d, device=d2l.try_gpu()) for d in (d_k, d_k, d_v))
K = F.normalize(K, dim=-1) # Unit-norm keys, throughout
beta = torch.sigmoid(torch.randn(T, device=d2l.try_gpu()))
y_delta = delta_recurrent(Q, K, V, beta)
err = (y_delta - delta_recurrent_matrix(Q, K, V, beta)).abs().max() \
/ y_delta.abs().max()
print(f'error form vs matrix form: relative deviation {float(err):.2e}')
assert err < 1e-5 # One rule, two readings