Skip to content

feat(compile-dag): scaledDotProductAttention backward (#1384) - #1489

Merged
michalharakal merged 1 commit into
developfrom
feat/1384-sdpa-backward
Nov 10, 2026
Merged

michalharakal merged 1 commit into
developfrom
feat/1384-sdpa-backward

Conversation

@michalharakal

Copy link
Copy Markdown
Contributor

Closes #1386, closes #1387. Part of #1384.

What

scaledDotProductAttentionBackward in DefaultExecutionTape returned null for every input, so training stopped at attention. This PR replaces the stub with the attention adjoint and removes the last entry from stubbedBackwardOpNames.

How

With S = s·Q·Kᵀ + masks, P = softmax(S), O = P·V and upstream dO:

dV = Pᵀ·dO
dP = dO·Vᵀ
dS = P ⊙ (dP − rowsum(dP ⊙ P))      (softmax backward, shared helper)
dQ = s·dS·K
dK = s·dSᵀ·Q
dM = dS reduced to the dense mask's shape
  • P is recomputed in the backward, not stored, as the issue asked.
  • The rule runs in a rank-5 layout [batch, kvHeads, nRep, seqQ, …]. Grouped-query K/V broadcast against the query heads of their group inside matmul, and dK/dV reduce over nRep with one sum. Gemma's layout works unchanged.
  • Causal, window, rightContext and keyLengths are rebuilt from AttentionMask.additive, the same definition the forward kernel reads, so a masked score has P = 0 and exactly zero gradient. The dense mask operand is added trailing-aligned exactly as in the forward and receives a gradient. keyLengths is integer and gets none.

Tests

SdpaBackwardTest (12 cases, skainet-compile-dag commonTest):

  • Central finite differences (eps 1e-2, tol 1e-2) for: plain, causal, grouped query (4 heads / 2 kv heads), key padding (padded keys get exactly 0f in dK and dV), window 2 with right context 1, dense mask [2,1,3,3], per-kv-head mask [1,2,1,3], a one-query decode step over 5 cached keys, and scale 0.5.
  • PyTorch 2.5.1 parity within 2e-4 against torch.nn.functional.scaled_dot_product_attention for plain, causal and grouped+scaled cases. Fixtures were generated in float64 with seed 1384 and are embedded in the test.

Full :skainet-compile:skainet-compile-dag:jvmTest: 129/129. apiCheck clean (private and internal changes only).

Acceptance criteria of #1384

  • Gradients match PyTorch autograd for causal and non-causal cases
  • scaledDotProductAttention removed from stubbedBackwardOpNames; ops matrix Backward column reads ✅ (59 / 59)

Notes for review

🤖 Generated with Claude Code

Replaces the stub that returned null gradients with the attention adjoint:

  dV = Pᵀ·dO        dP = dO·Vᵀ        dS = P ⊙ (dP − rowsum(dP ⊙ P))
  dQ = s·dS·K       dK = s·dSᵀ·Q      dM = dS summed to the mask's shape

P is recomputed, not stored. The rule runs in a rank-5 layout
[batch, kvHeads, nRep, seqQ, …], so grouped-query K/V broadcast against
the query heads of their group in matmul and their gradients reduce with
one sum. The structural rules (causal, window, rightContext, keyLengths)
are rebuilt from AttentionMask.additive, the definition the forward
kernel reads, so a masked score has P = 0 and exactly zero gradient; the
dense mask is added trailing-aligned as in the forward. keyLengths is
integer and gets no gradient.

SdpaBackwardTest: central finite differences for plain, causal, grouped
query, key padding (padded keys get exactly 0), window with right
context, dense mask (gradient on the mask, also per kv head), a decode
step with one query, and a custom scale; PyTorch 2.5.1 float64 parity
within 2e-4 for plain, causal and grouped+scaled cases.

stubbedBackwardOpNames is empty; the ops matrix Backward column reads
59/59.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@michalharakal michalharakal added training Training pipeline, optimizers, loss coding Implementation task (DARC: C) labels Nov 10, 2026
@github-actions

Copy link
Copy Markdown

📖 Documentation Preview

The documentation has been built successfully for this PR.

Generated Files:

  • Operator documentation: docs/modules/operators/_generated_/
  • JSON schema output: operators.json

Artifacts:

  • Download the documentation-preview-1489 artifact to view the complete documentation locally.

This comment will be updated automatically when the PR is updated.

@michalharakal
michalharakal merged commit dd1a98f into develop Nov 10, 2026
19 checks passed
@michalharakal
michalharakal deleted the feat/1384-sdpa-backward branch November 10, 2026 19:59
@michalharakal

Copy link
Copy Markdown
Contributor Author

Added TransformerBlockBackwardTest: a whole pre-norm Transformer block (gather embeddings, RMSNorm from primitives, 2-head causal SDPA, gated GELU FFN, class-token head) with all 204 parameter entries checked against central finite differences, worst diff 0.0017 at tol 0.02. End-to-end evidence that nothing in a block stops the gradient anymore.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

coding Implementation task (DARC: C) training Training pipeline, optimizers, loss

Projects

None yet

2 participants