S1-31: SSM_SCAN_BACK — Mamba has a backward - #25
Open
dillon-blake wants to merge 1 commit into
Open
Conversation
The selective scan's VJP: a REVERSE recurrence, walking t from n_t-1 down to 0 and
carrying the state gradient backwards. Five gradients out of one op (d_s, d_x, d_dt, d_B,
d_C), packed exactly as ggml_ssm_scan itself packs y with the final states.
Both branches: Mamba-2 (one scalar decay per head) and Mamba-1 (one per state).
TWO THINGS THAT ARE EASY TO GET WRONG, AND BOTH ARE SILENT.
1. `grad` is the WHOLE packed gradient of the forward's dst -- y AND the final states --
and the state region is NOT zero. MODE_GRAD's objective sums over the packed dst, so
d(sum)/d(s_final) is 1, and a kernel that seeded the reverse recurrence with zeros would
disagree with the finite difference and be WRONG TO. It seeds ds at t = n_t.
That is not a hypothetical: seeding ds with zeros is one of the five mutations below,
and it measures MAA 0.59. In training the region genuinely IS zero -- the cached state
feeds nothing downstream of the loss -- so honouring it costs nothing there, and it makes
cross-ubatch BPTT nearly free later.
2. The forward OVERWRITES its state in place, so s_{t-1} is gone by the time the backward
needs it -- and it does need it, for d(dt) via the dA path. The states are recomputed and
STORED, all n_t + 1 of them. Store-all: correct, O(n_t) memory, and the honest starting
point for a CPU oracle. Checkpoint-every-K is the optimization and must be pinned
bit-for-bit against this.
Threaded by SEQUENCE, not by head. dB and dC accumulate over every head in a group, so a
head-partitioned kernel would have several threads writing the same (i0, g, t) and would
need atomics -- neither deterministic nor free. One thread per sequence owns every output
it touches. Parallelism is n_seqs, which is small; correctness and determinism (ADR-0002)
come first in the kernel the GPU ports get measured against.
THE ORACLE IS A FLOAT64 FINITE DIFFERENCE OF GGML'S OWN FORWARD, and it has to be.
A reverse recurrence has many ways to be subtly wrong -- a dropped dA path, a state read
one token late, a missing seed -- and a hand-written reference would share my derivation's
bugs. So each of the five gradients is checked by perturbing every input element and
re-running ggml_ssm_scan itself, under a NON-UNIFORM objective that includes the packed
state region. All five exact to ~1e-5 (float32 FD precision), in both branches.
max_maa_err 5e-2, measured: noise floor 2.1e-2 (the recurrence is exponential in dt*A, so a
finite difference of it amplifies its own rounding), five injected mutations at 0.38-0.59,
all five caught. 2.4x above the noise, 7.5-12x below every defect.
The tests called ggml_set_param zero times before S1-29b, and every shape was above
grad_nmax, so `grad -o SSM_SCAN` reported OK while the op had no backward at all.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Stacked on #24 (S1-30). Mamba has a backward.
The selective scan's VJP: a reverse recurrence, walking
tfromn_t-1down to 0 and carrying the state gradient backwards. Five gradients out of one op (d_s,d_x,d_dt,d_B,d_C), packed exactly asggml_ssm_scanitself packsywith the final states. Both branches: Mamba-2 (one scalar decay per head) and Mamba-1 (one per state).Two things that are easy to get wrong, and both are silent
1.
gradis the WHOLE packed gradient —yand the final states — and the state region is not zero. MODE_GRAD's objective sums over the packed dst, sod(sum)/d(s_final)is 1, and a kernel that seeded the reverse recurrence with zeros would disagree with the finite difference and be right to. It seedsdsatt = n_t.That isn't hypothetical: seeding with zeros is one of the five mutations below and it measures MAA 0.59. In training the region genuinely is zero — the cached state feeds nothing downstream of the loss — so honouring it costs nothing there, and it makes cross-ubatch BPTT nearly free later.
2. The forward overwrites its state in place, so
s_{t-1}is gone by the time the backward needs it — and it does need it, ford(dt)via thedApath. The states are recomputed and stored, alln_t + 1of them. Store-all: correct,O(n_t)memory, and the honest starting point for a CPU oracle. Checkpoint-every-K is the optimization and must be pinned bit-for-bit against this.Threaded by sequence, not by head
dBanddCaccumulate over every head in a group, so a head-partitioned kernel would have several threads writing the same(i0, g, t)and would need atomics — neither deterministic nor free. One thread per sequence owns every output it touches. Parallelism isn_seqs, which is small; correctness and determinism (ADR-0002) come first in the kernel the GPU ports will be measured against.The oracle is a float64 finite difference of ggml's own forward, and it has to be
A reverse recurrence has many ways to be subtly wrong — a dropped
dApath, a state read one token late, a missing seed — and a hand-written reference would share my derivation's bugs. So each of the five gradients is checked by perturbing every input element and re-runningggml_ssm_scanitself, under a non-uniform objective that includes the packed state region.All five exact to ~1e-5 (float32 FD precision), in both branches.
Verification
max_maa_err = 5e-2, measured:dApath fromd(dt)dswith zeros instead of the packed state gradienty's own contribution todSs_{t-1}wheres_tbelongs indCFive mutations injected, five caught. 2.4x above the noise, 7.5–12x below every defect. The noise is high because the recurrence is exponential in
dt*A, so a finite difference of it amplifies its own rounding — which is precisely why the float64 reference is the oracle and this is the wiring-plus-sanity check.Before S1-29b these tests called
ggml_set_paramzero times and every shape was abovegrad_nmax, sograd -o SSM_SCANreportedOKwhile the op had no backward at all.