@staticmethod
@once_differentiable
def backward(ctx: Any, grad_output: torch.Tensor) -> tuple:
"""Collect G/N, inject nonzero dHT and finish raw-input gradients."""
ops = _load_state_ops()
value, gate, beta_raw, a_log, dt_bias, *saved = ctx.saved_tensors
(gk, aqk, akk, weights, _, qg, kg, _, query, key,
query_rstd, key_rstd, beta, hidden, v_new) = saved
packed = bool(ctx.native_metadata)
reception = ctx.exchange.receive(query, False)
grad = grad_output.transpose(1, 2).contiguous()
d_aqk, dv_local, dq_raw = ops.chunk_kda_bwd_prepare(
*(_native_backward_view(tensor, packed) for tensor in (aqk, v_new, grad, hidden)), scale=ctx.scale,
layout="NTD" if packed else "BNSD", chunk_size=64, **ctx.native_metadata)
dv_local_head = dv_local.unsqueeze(0) if packed else dv_local
maps = None
if ctx.exchange.needs_summary(False):
# The stock summary ABI requires model-dtype gk. Keep the original
# FP32 cumulative gate for local dhu/finalize; only this input is rounded.
maps = ops.pre_process_bwd_kernel_merged(
qg, kg, weights, grad, dv_local_head, ctx.scale, 64,
gk=gk.to(qg.dtype), cu_seqlens=ctx.head)
terminal, send_handle, outgoing = ctx.exchange.exchange(ops, maps, reception, False, ctx.suffix)
del maps, reception
if ctx.exchange.info is not None:
terminal = ctx.exchange.info.last_state(terminal)
# Communication and summary apply retain FP32. Convert once at the
# stock dhu input; its local state gradient output is not used by CP.
terminal = None if terminal is None else terminal.to(qg.dtype)
hidden_grad, _, dv_scan = ops.chunk_gated_delta_rule_bwd_dhu(
qg, kg, weights, grad, dv_local_head, ctx.scale, 64,
gK=gk, h0=None, dht=terminal, use_exp2=True, **ctx.native_metadata)
dq, dk, dv, dbeta, dgate, da_log, ddt_bias = ops.chunk_kda_bwd_finalize(
*(_native_backward_view(tensor, packed)
for tensor in (query, key, value, gk, gate.transpose(1, 2).contiguous(), beta)),
a_log, dt_bias,
*(_native_backward_view(tensor, packed) for tensor in (akk, v_new, hidden, hidden_grad, dv_scan)),