Skip to content

fix(autograd): CrossEntropyLoss tape detach, softmax/variance backward on rank>=3 - #877

Merged
michalharakal merged 1 commit into
developfrom
fix/autograd-862-863-864
Jul 24, 2026
Merged

michalharakal merged 1 commit into
developfrom
fix/autograd-862-863-864

Conversation

@michalharakal

Copy link
Copy Markdown
Contributor

Fixes three autograd correctness bugs that cause silently frozen or crashing training — the "wrong but no error" class.

#862 — CrossEntropyLoss detaches the tape

The index-target path built its result host-side (tensorDataFactory.init + fromData), reading logProbs values out of the tensor. The resulting loss had no recorded parents, so backward stopped there and the predictions got a null/zero gradient — training froze at constant loss with no error. The soft-target path had a subtler variant: targets * logProbs dispatches through targets.ops, so it only recorded when the targets happened to live on the recording context.

Fix: compute the NLL with differentiable ops dispatched through the predictions' ops. The index path builds a constant one-hot selector (reading discrete indices host-side is fine — they carry no gradient) and computes -sum(oneHot * logProbs); the soft path multiplies via logProbs.ops. Loss values are unchanged; the tape stays connected.

#863 — softmax/logSoftmax backward crashes on negative dim, rank ≥ 3

softmax(dim = -1) passed -1 straight to broadcastToInput, which reserves -1 as a "no-unsqueeze" sentinel — so the reduced axis was never re-expanded and the subsequent elementwise op threw Shapes [2,2,4,4] and [2,2,4] cannot be broadcasted. Any attention-style model using the natural softmax(dim = -1) spelling crashed in backward.

Fix: softmaxGrad/logSoftmaxGrad normalize a negative dim (dim + rank) before use. Softmax always reduces exactly one real axis, so this is unambiguous and leaves the sentinel semantics of sumGrad/meanGrad intact.

#864 — varianceBackward mis-broadcasts on rank ≥ 2

mean(x, dim) dropped the reduced axis (no keepdim), so x - mean could not broadcast; N used the total volume instead of the axis size; and the reduced-shape upstream was never expanded back. A LayerNorm built as (x - mean) / sqrt(variance(x, dim) + eps) crashed in backward.

Fix: re-expand the mean and upstream over the reduced axis (unsqueeze / broadcastToInput) and use the axis size for N.

Tests

  • OpsAutodiffBackwardTest: finite-difference backward checks for softmax(dim=-1), logSoftmax(dim=-1), and variance(dim=2) on rank-3 tensors (non-uniform upstream so a wrong gradient is detectable).
  • CrossEntropyBackwardTest (new): index-target CE gradient matches the exact analytic (softmax − oneHot)/N; soft-target CE records a non-zero gradient even when the targets live on a separate eager context.
  • Full jvmTest for skainet-lang-core, skainet-compile-dag, and skainet-backend-cpu green.

Found while porting an educational GPT trainer to SKaiNET; each bug had a precise reproduction there (training froze / crashed until worked around). This removes the need for those workarounds.

…d on rank>=3

Three autograd correctness bugs that caused silently-frozen or crashing
training:

- CrossEntropyLoss (#862): the index-target path built its result
  host-side via tensorDataFactory + fromData, detaching the tape so no
  gradient reached the predictions; the soft-target path only recorded
  when targets lived on the recording context. Both now compute the NLL
  with differentiable ops dispatched through the predictions' ops (a
  constant one-hot for the index path), keeping the tape connected.

- softmax/logSoftmax backward (#863): a negative dim (e.g. softmax(dim=-1))
  was passed straight to broadcastToInput, which reserves -1 as a
  no-unsqueeze sentinel, so the reduced axis was never re-expanded and
  backward crashed for rank>=3. softmaxGrad/logSoftmaxGrad now normalize
  the dim.

- varianceBackward (#864): the reduced mean and upstream kept the reduced
  shape, so the subtract/multiply could not broadcast for rank>=2, and N
  used the total volume instead of the axis size. Both are now expanded
  over the reduced axis and N is the axis size.

Adds finite-difference backward tests for softmax/logSoftmax/variance on
rank-3 tensors and analytic-gradient tests for index- and soft-target
CrossEntropyLoss.

Closes #862, #863, #864
@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-877 artifact to view the complete documentation locally.

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

@michalharakal
michalharakal requested a review from aharakal July 24, 2026 17:18
@michalharakal
michalharakal merged commit 8f63356 into develop Jul 24, 2026
18 of 24 checks passed
@michalharakal
michalharakal deleted the fix/autograd-862-863-864 branch July 24, 2026 17:20
MacOS pushed a commit to MacOS/SKaiNET that referenced this pull request Jul 27, 2026
Bump version 0.36.0 -> 0.37.0 (gradle.properties, docs/antora.yml,
README quickstart). Promote CHANGELOG [Unreleased] to [0.37.0]: Lstm
layer (SKaiNET-developers#824), real Dropout masking (SKaiNET-developers#867), LR schedules and mutable
optimizer lr (SKaiNET-developers#866), optional-bias and open Linear (SKaiNET-developers#870, SKaiNET-developers#875),
androidNative IO targets (SKaiNET-developers#836, SKaiNET-developers#842, SKaiNET-developers#845), the SDPA default-scale fix
(SKaiNET-developers#880), three autograd fixes (SKaiNET-developers#877), the argMax DAG output spec (SKaiNET-developers#878),
tokenizer BPE inference and N-D gather (SKaiNET-developers#879), plus the CI/docs
supply-chain hardening and toolchain bumps. Refresh README "What's New"
and add a Contributors (0.37.0) section.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants