Skip to content

fix(compile-dag): concat and squeeze backward on shapes they got wrong (#1490) - #1491

Merged
michalharakal merged 2 commits into
developfrom
fix/autograd-concat-squeeze-backward
Nov 10, 2026
Merged

michalharakal merged 2 commits into
developfrom
fix/autograd-concat-squeeze-backward

Conversation

@michalharakal

Copy link
Copy Markdown
Contributor

Closes #1490.

Two @Diff ops with a wired dispatch arm whose backward returned a wrong result without an error, so neither the coverage matrix nor AutodiffCoverageTest could see them.

concatBackward

The rule split the upstream by one input's size along dim and picked chunk offset / size. That only lines up when every input has the same extent along the axis. For concat([a: [2,1], b: [2,2]], dim = 1) under a position-weighted loss, b got [1, 2, 4, 5] instead of [2, 3, 5, 6]; shapes where split threw were caught and turned into null.

Now each input gets narrow(upstream, dim, offset, size) at the offset the forward placed it, offsets accumulated in input order, negative dim normalised, and a size-0 input still gets null.

squeezeBackward

squeeze(x) with no axis removes every size-1 axis, but the rule was unsqueeze(upstream, dim ?: 0), so x: [1, 3, 1] got a [1, 3] gradient. Squeeze only drops size-1 axes, so the gradient is simply the upstream reshaped to the input's shape, whichever axes were removed.

Tests

ConcatSqueezeBackwardTest (7 cases): exact gradients under a loss whose weight at each output position is its row-major index + 1, so every position's gradient is known. Unequal concat on dim 1, on dim 0 with three inputs, with a negative dim; equal concat as a regression; squeeze() and squeeze(dim) shapes; a squeeze-then-concat chain.

:skainet-compile:skainet-compile-dag:jvmTest 124/124, apiCheck clean.

Out of scope

convert has no backward, so gradients through PrecisionBoundary and the FP16/BF16 branches of layer/group norm are zero. That is SKEEP-008 phase A3 (#1433).

🤖 Generated with Claude Code

#1490)

concatBackward split the upstream gradient by one input's size and
picked chunk `offset / size`, which only lines up when every input has
the same extent along the concat axis; other shapes received another
input's slice, or null from a caught exception, without an error. Each
input now gets its own narrow of the upstream at the offset the forward
placed it, for any sizes and a negative dim.

squeezeBackward unsqueezed dim 0 alone, so squeeze(x) with no axis on
x: [1, 3, 1] produced a [1, 3] gradient. Squeeze only drops size-1 axes,
so the gradient is the upstream reshaped to the input's shape.

ConcatSqueezeBackwardTest: exact gradients under a position-weighted
loss for unequal concat (dim 0, dim 1, negative dim, three inputs),
equal concat, squeeze with and without an axis, and a squeeze-then-
concat chain.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@michalharakal michalharakal added the training Training pipeline, optimizers, loss label Nov 10, 2026
@michalharakal
michalharakal merged commit 72418a9 into develop Nov 10, 2026
17 checks passed
@michalharakal michalharakal mentioned this pull request Nov 11, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

training Training pipeline, optimizers, loss

Projects

None yet

Development

Successfully merging this pull request may close these issues.

concatBackward returns wrong gradients for inputs of different sizes; squeezeBackward with no axis returns the wrong shape

2 participants