1#![allow(non_snake_case)]
41
42use super::recalculated::{
43 Mamba3MomentsBackendExt, RotPrim, chunk_decay, chunk_mask, chunk_states, chunk_writes,
44 quat_conj_prim, quat_mul_prim, rotate_chunk,
45};
46use crate::utils::fprim::F;
47use burn::backend::autodiff::{
48 Autodiff,
49 checkpoint::{base::Checkpointer, strategy::CheckpointStrategy},
50 grads::Gradients,
51 ops::{Backward, Ops, OpsKind},
52};
53use burn::backend::tensor::FloatTensor;
54use burn::backend::{Backend, BackendTypes};
55
56struct MomentsGrads<B: Backend> {
58 d_xhat_bnlchp: F<B, 6>,
59 d_bhat_bnlchr: F<B, 6>,
60 d_da_bnlh: F<B, 4>,
61 d_rot: <B as BackendTypes>::FloatTensorPrimitive,
62 d_initial_bhpr: F<B, 4>,
63}
64
65fn d_theta_chunk<B: Backend>(
67 phys_bhlpr: &F<B, 5>,
68 d_phys_bhlpr: &F<B, 5>,
69 num_angles_total: usize,
70 rope_dim: usize,
71 rotate_pairwise: bool,
72) -> F<B, 4> {
73 let [batch, nheads, len, _p, state_rank] = phys_bhlpr.dims();
74 if rope_dim == 0 {
75 let device = phys_bhlpr.device();
76 let dtype = phys_bhlpr.dtype();
77 return F::<B, 4>::zeros([batch, nheads, len, num_angles_total], &device, dtype);
78 }
79 let num_angles = rope_dim / 2;
80 let (px, py, dx, dy) = if rotate_pairwise {
81 let split = |t: &F<B, 5>| {
82 let a = t.clone().narrow(4, 0, rope_dim).reshape([
83 batch,
84 nheads,
85 len,
86 phys_bhlpr.dims()[3],
87 num_angles,
88 2,
89 ]);
90 (
91 a.clone().narrow(5, 0, 1).squeeze_dim::<5>(5),
92 a.narrow(5, 1, 1).squeeze_dim::<5>(5),
93 )
94 };
95 let (px, py) = split(phys_bhlpr);
96 let (dx, dy) = split(d_phys_bhlpr);
97 (px, py, dx, dy)
98 } else {
99 let half = state_rank / 2;
100 (
101 phys_bhlpr.clone().narrow(4, 0, num_angles),
102 phys_bhlpr.clone().narrow(4, half, num_angles),
103 d_phys_bhlpr.clone().narrow(4, 0, num_angles),
104 d_phys_bhlpr.clone().narrow(4, half, num_angles),
105 )
106 };
107 (dx * py - dy * px).sum_dim(3).squeeze_dim::<4>(3)
109}
110
111fn d_quat_chunk<B: Backend>(
114 states_bhlpr: &F<B, 5>,
115 d_phys_bhlpr: &F<B, 5>,
116 blocks: usize,
117) -> F<B, 5> {
118 let [batch, nheads, len, per_head_dim, _r] = states_bhlpr.dims();
119 let rope_width = 4 * blocks;
120 let as_blocks = |t: &F<B, 5>| {
121 t.clone().narrow(4, 0, rope_width).reshape([
122 batch,
123 nheads,
124 len,
125 per_head_dim,
126 blocks,
127 4,
128 ])
129 };
130 quat_mul_prim(as_blocks(d_phys_bhlpr), quat_conj_prim(as_blocks(states_bhlpr)))
131 .sum_dim(3)
132 .squeeze_dim::<5>(3)
133}
134
135fn scatter_last<B: Backend>(d_carry_bhpr: F<B, 4>, chunk_len: usize) -> F<B, 5> {
138 let [batch, nheads, per_head_dim, state_rank] = d_carry_bhpr.dims();
139 let device = d_carry_bhpr.device();
140 let dtype = d_carry_bhpr.dtype();
141 let dc = d_carry_bhpr.unsqueeze_dim::<5>(2);
142 if chunk_len == 1 {
143 dc
144 } else {
145 F::cat(
146 vec![
147 F::<B, 5>::zeros(
148 [batch, nheads, chunk_len - 1, per_head_dim, state_rank],
149 &device,
150 dtype,
151 ),
152 dc,
153 ],
154 2,
155 )
156 }
157}
158
159fn rev_cumsum<B: Backend, const D: usize>(t: F<B, D>, dim: usize) -> F<B, D> {
161 t.flip(&[dim]).cumsum(dim).flip(&[dim])
162}
163
164fn boundary_carries<B: Backend>(
167 xhat_bnlchp: &F<B, 6>,
168 bhat_bnlchr: &F<B, 6>,
169 da_bnlh: &F<B, 4>,
170 initial_state_bhpr: &F<B, 4>,
171 processed: usize,
172) -> Vec<F<B, 4>> {
173 let [batch, _n, chunk_len, chan, nheads, per_head_dim] = xhat_bnlchp.dims();
174 let [.., state_rank] = bhat_bnlchr.dims();
175
176 let mut h_bhpr = initial_state_bhpr.clone();
177 let mut carries = Vec::with_capacity(processed);
178 for n in 0..processed {
179 carries.push(h_bhpr.clone());
180 if n + 1 == processed {
181 break;
182 }
183 let a_bhl = da_bnlh
184 .clone()
185 .narrow(1, n, 1)
186 .squeeze_dim::<3>(1)
187 .permute([0, 2, 1])
188 .cumsum(2);
189 let a_last_bh1 = a_bhl.clone().narrow(2, chunk_len - 1, 1);
190 let scale_bhj = (a_last_bh1
191 .clone()
192 .expand([batch, nheads, chunk_len])
193 - a_bhl)
194 .exp();
195 let xh_bhjcp = xhat_bnlchp
196 .clone()
197 .narrow(1, n, 1)
198 .squeeze_dim::<5>(1)
199 .permute([0, 3, 1, 2, 4]);
200 let bh_bhJr = bhat_bnlchr
201 .clone()
202 .narrow(1, n, 1)
203 .squeeze_dim::<5>(1)
204 .permute([0, 3, 1, 2, 4])
205 .reshape([batch, nheads, chunk_len * chan, state_rank]);
206 let xw_bhJp = (xh_bhjcp
207 * scale_bhj
208 .unsqueeze_dims::<5>(&[3, 4])
209 .expand([batch, nheads, chunk_len, chan, per_head_dim]))
210 .reshape([batch, nheads, chunk_len * chan, per_head_dim]);
211 let contribution_bhpr = xw_bhJp.permute([0, 1, 3, 2]).matmul(bh_bhJr);
212 let d_last_bhpr = a_last_bh1
213 .exp()
214 .unsqueeze_dim::<4>(3)
215 .expand([batch, nheads, per_head_dim, state_rank]);
216 h_bhpr = d_last_bhpr * h_bhpr + contribution_bhpr;
217 }
218 carries
219}
220
221#[allow(clippy::too_many_arguments)]
223fn moments_phys_bwd<B: Backend>(
224 d_m2_bhrr: F<B, 4>,
225 d_m1_bhr: F<B, 3>,
226 xhat_bnlchp: F<B, 6>,
227 bhat_bnlchr: F<B, 6>,
228 da_bnlh: F<B, 4>,
229 rot: RotPrim<B>,
230 initial_state_bhpr: F<B, 4>,
231 valid_len: usize,
232) -> MomentsGrads<B> {
233 let [batch, nchunks, chunk_len, chan, nheads, per_head_dim] = xhat_bnlchp.dims();
234 let [.., state_rank] = bhat_bnlchr.dims();
235 let device = xhat_bnlchp.device();
236 let dtype = xhat_bnlchp.dtype();
237 let processed = valid_len.div_ceil(chunk_len).min(nchunks);
238
239 let h_ins = boundary_carries::<B>(
240 &xhat_bnlchp,
241 &bhat_bnlchr,
242 &da_bnlh,
243 &initial_state_bhpr,
244 processed,
245 );
246 let d_m2sym_bhrr = d_m2_bhrr.clone() + d_m2_bhrr.permute([0, 1, 3, 2]);
247
248 let zero_dx = || F::<B, 5>::zeros([batch, chunk_len, chan, nheads, per_head_dim], &device, dtype);
249 let zero_db = || F::<B, 5>::zeros([batch, chunk_len, chan, nheads, state_rank], &device, dtype);
250 let zero_dda = || F::<B, 3>::zeros([batch, chunk_len, nheads], &device, dtype);
251 let mut d_xhat_chunks: Vec<F<B, 5>> = (processed..nchunks).map(|_| zero_dx()).collect();
252 let mut d_bhat_chunks: Vec<F<B, 5>> = (processed..nchunks).map(|_| zero_db()).collect();
253 let mut d_da_chunks: Vec<F<B, 3>> = (processed..nchunks).map(|_| zero_dda()).collect();
254 let mut d_rot_chunks_rev: Vec<<B as BackendTypes>::FloatTensorPrimitive> = Vec::new();
256
257 let mut d_carry_bhpr = F::<B, 4>::zeros([batch, nheads, per_head_dim, state_rank], &device, dtype);
258
259 for n in (0..processed).rev() {
260 let start = n * chunk_len;
261 let da_blh = da_bnlh.clone().narrow(1, n, 1).squeeze_dim::<3>(1);
263 let (d_bhl, l_bhll) = chunk_decay::<B>(da_blh);
264 let xhat_blchp = xhat_bnlchp.clone().narrow(1, n, 1).squeeze_dim::<5>(1);
265 let bhat_blchr = bhat_bnlchr.clone().narrow(1, n, 1).squeeze_dim::<5>(1);
266 let states_bhlpr =
267 chunk_states::<B>(xhat_blchp.clone(), bhat_blchr.clone(), &d_bhl, &l_bhll, &h_ins[n]);
268 let phys_bhlpr = rotate_chunk::<B>(states_bhlpr.clone(), &rot, start, false);
269
270 let mask_bhlpr = chunk_mask::<B>(chunk_len, start, valid_len, &device, dtype).expand([
272 batch,
273 nheads,
274 chunk_len,
275 per_head_dim,
276 state_rank,
277 ]);
278 let d_phys_bhlpr = (phys_bhlpr.clone().matmul(
279 d_m2sym_bhrr
280 .clone()
281 .unsqueeze_dim::<5>(2)
282 .expand([batch, nheads, chunk_len, state_rank, state_rank]),
283 ) + d_m1_bhr
284 .clone()
285 .unsqueeze_dims::<5>(&[2, 3])
286 .expand([batch, nheads, chunk_len, per_head_dim, state_rank]))
287 * mask_bhlpr;
288
289 let d_states_bhlpr = rotate_chunk::<B>(d_phys_bhlpr.clone(), &rot, start, true);
291 let d_rot_chunk = match &rot {
292 RotPrim::Complex {
293 cum_bsha,
294 rope_dim,
295 rotate_pairwise,
296 } => {
297 let num_angles_total = cum_bsha.dims()[3];
298 d_theta_chunk::<B>(
299 &phys_bhlpr,
300 &d_phys_bhlpr,
301 num_angles_total,
302 *rope_dim,
303 *rotate_pairwise,
304 )
305 .permute([0, 2, 1, 3]) .inner()
307 }
308 RotPrim::Quaternion { cum_bshj4 } => {
309 let blocks = cum_bshj4.dims()[3];
310 d_quat_chunk::<B>(&states_bhlpr, &d_phys_bhlpr, blocks)
311 .permute([0, 2, 1, 3, 4]) .inner()
313 }
314 };
315 d_rot_chunks_rev.push(d_rot_chunk);
316
317 let g_bhlpr = d_states_bhlpr + scatter_last::<B>(d_carry_bhpr.clone(), chunk_len);
319
320 d_carry_bhpr = (g_bhlpr.clone()
322 * d_bhl
323 .clone()
324 .unsqueeze_dims::<5>(&[3, 4])
325 .expand([batch, nheads, chunk_len, per_head_dim, state_rank]))
326 .sum_dim(2)
327 .squeeze_dim::<4>(2);
328
329 let g_bhtPR = g_bhlpr
331 .clone()
332 .reshape([batch, nheads, chunk_len, per_head_dim * state_rank]);
333 let dw_bhjpr = l_bhll
334 .clone()
335 .permute([0, 1, 3, 2])
336 .matmul(g_bhtPR.clone())
337 .reshape([batch, nheads, chunk_len, per_head_dim, state_rank]);
338 let xh_bhjcp = xhat_blchp.clone().permute([0, 3, 1, 2, 4]);
339 let bh_bhjcr = bhat_blchr.clone().permute([0, 3, 1, 2, 4]);
340 let d_xh_bhjcp = bh_bhjcr.matmul(dw_bhjpr.clone().permute([0, 1, 2, 4, 3]));
341 let d_bh_bhjcr = xh_bhjcp.matmul(dw_bhjpr);
342 d_xhat_chunks.push(d_xh_bhjcp.permute([0, 2, 3, 1, 4])); d_bhat_chunks.push(d_bh_bhjcr.permute([0, 2, 3, 1, 4])); let w_bhjpr = chunk_writes::<B>(xhat_blchp, bhat_blchr);
347 let scal_bhl = (g_bhlpr
348 * h_ins[n]
349 .clone()
350 .unsqueeze_dim::<5>(2)
351 .expand([batch, nheads, chunk_len, per_head_dim, state_rank]))
352 .sum_dim(4)
353 .sum_dim(3)
354 .reshape([batch, nheads, chunk_len])
355 * d_bhl;
356 let gw_bhtj = g_bhtPR.matmul(
357 w_bhjpr
358 .reshape([batch, nheads, chunk_len, per_head_dim * state_rank])
359 .permute([0, 1, 3, 2]),
360 );
361 let a_bhtj = gw_bhtj * l_bhll;
362 let row_bhl = a_bhtj.clone().sum_dim(3).squeeze_dim::<3>(3);
363 let col_bhl = a_bhtj.sum_dim(2).squeeze_dim::<3>(2);
364 let d_da_bhl = rev_cumsum::<B, 3>(scal_bhl + row_bhl, 2) - rev_cumsum::<B, 3>(col_bhl, 2);
365 d_da_chunks.push(d_da_bhl.permute([0, 2, 1])); }
367
368 fn reorder<T>(mut v: Vec<T>, nchunks: usize, processed: usize) -> Vec<T> {
371 let tail = v.split_off(nchunks - processed); let mut ordered: Vec<T> = tail.into_iter().rev().collect();
373 ordered.extend(v); ordered
375 }
376
377 let d_xhat_bnlchp = F::stack::<6>(reorder(d_xhat_chunks, nchunks, processed), 1);
378 let d_bhat_bnlchr = F::stack::<6>(reorder(d_bhat_chunks, nchunks, processed), 1);
379 let d_da_bnlh = F::stack::<4>(reorder(d_da_chunks, nchunks, processed), 1);
380
381 let d_rot = {
384 let pad = (nchunks - processed) * chunk_len;
385 match &rot {
386 RotPrim::Complex { cum_bsha, .. } => {
387 let num_angles = cum_bsha.dims()[3];
388 let mut chunks: Vec<F<B, 4>> = d_rot_chunks_rev
389 .into_iter()
390 .rev()
391 .map(F::<B, 4>::new)
392 .collect();
393 if pad > 0 {
394 chunks.push(F::<B, 4>::zeros(
395 [batch, pad, nheads, num_angles],
396 &device,
397 dtype,
398 ));
399 }
400 F::cat(chunks, 1).inner()
401 }
402 RotPrim::Quaternion { cum_bshj4 } => {
403 let blocks = cum_bshj4.dims()[3];
404 let mut chunks: Vec<F<B, 5>> = d_rot_chunks_rev
405 .into_iter()
406 .rev()
407 .map(F::<B, 5>::new)
408 .collect();
409 if pad > 0 {
410 chunks.push(F::<B, 5>::zeros(
411 [batch, pad, nheads, blocks, 4],
412 &device,
413 dtype,
414 ));
415 }
416 F::cat(chunks, 1).inner()
417 }
418 }
419 };
420
421 MomentsGrads {
422 d_xhat_bnlchp,
423 d_bhat_bnlchr,
424 d_da_bnlh,
425 d_rot,
426 d_initial_bhpr: d_carry_bhpr,
427 }
428}
429
430impl<B: Backend + Mamba3MomentsBackendExt, C: CheckpointStrategy> Mamba3MomentsBackendExt
431 for Autodiff<B, C>
432{
433 fn mamba3_state_moments_phys(
434 xhat_bnlchp: FloatTensor<Self>,
435 bhat_bnlchr: FloatTensor<Self>,
436 da_bnlh: FloatTensor<Self>,
437 rot: FloatTensor<Self>,
438 initial_state_bhpr: FloatTensor<Self>,
439 valid_len: usize,
440 quaternion: bool,
441 rope_dim: usize,
442 rotate_pairwise: bool,
443 ) -> (FloatTensor<Self>, FloatTensor<Self>) {
444 #[derive(Debug)]
446 struct MomentsBackward;
447
448 #[derive(Clone, Debug)]
449 struct State<B: Backend> {
450 xhat: <B as BackendTypes>::FloatTensorPrimitive,
451 bhat: <B as BackendTypes>::FloatTensorPrimitive,
452 da: <B as BackendTypes>::FloatTensorPrimitive,
453 rot: <B as BackendTypes>::FloatTensorPrimitive,
454 initial: <B as BackendTypes>::FloatTensorPrimitive,
455 flat_len_m2: usize,
456 flat_len_m1: usize,
457 shape_m2: [usize; 4],
458 shape_m1: [usize; 3],
459 valid_len: usize,
460 quaternion: bool,
461 rope_dim: usize,
462 rotate_pairwise: bool,
463 }
464
465 impl<B: Backend + Mamba3MomentsBackendExt> Backward<B, 5> for MomentsBackward {
466 type State = State<B>;
467
468 fn backward(
469 self,
470 ops: Ops<Self::State, 5>,
471 grads: &mut Gradients,
472 _checkpointer: &mut Checkpointer,
473 ) {
474 let [node_xhat, node_bhat, node_da, node_rot, node_initial] = ops.parents;
475 let d_combined: <B as BackendTypes>::FloatTensorPrimitive =
476 grads.consume::<B>(&ops.node);
477
478 let State {
479 xhat,
480 bhat,
481 da,
482 rot,
483 initial,
484 flat_len_m2,
485 flat_len_m1,
486 shape_m2,
487 shape_m1,
488 valid_len,
489 quaternion,
490 rope_dim,
491 rotate_pairwise,
492 } = ops.state;
493
494 let (d_m2, d_m1) = crate::utils::combined_grad::unflatten_pair::<B, 4, 3>(
495 d_combined,
496 flat_len_m2,
497 flat_len_m1,
498 shape_m2,
499 shape_m1,
500 );
501
502 let grads_all = moments_phys_bwd::<B>(
503 F::<B, 4>::new(d_m2),
504 F::<B, 3>::new(d_m1),
505 F::<B, 6>::new(xhat),
506 F::<B, 6>::new(bhat),
507 F::<B, 4>::new(da),
508 RotPrim::<B>::wrap(rot, quaternion, rope_dim, rotate_pairwise),
509 F::<B, 4>::new(initial),
510 valid_len,
511 );
512
513 if let Some(n) = node_xhat {
514 grads.register::<B>(n.id, grads_all.d_xhat_bnlchp.inner());
515 }
516 if let Some(n) = node_bhat {
517 grads.register::<B>(n.id, grads_all.d_bhat_bnlchr.inner());
518 }
519 if let Some(n) = node_da {
520 grads.register::<B>(n.id, grads_all.d_da_bnlh.inner());
521 }
522 if let Some(n) = node_rot {
523 grads.register::<B>(n.id, grads_all.d_rot);
524 }
525 if let Some(n) = node_initial {
526 grads.register::<B>(n.id, grads_all.d_initial_bhpr.inner());
527 }
528 }
529 }
530
531 use burn::backend::TensorMetadata;
533 let [batch, _n, _l, _c, nheads, _p] = xhat_bnlchp.primitive.shape().dims();
534 let [.., state_rank] = bhat_bnlchr.primitive.shape().dims::<6>();
535 let shape_m2: [usize; 4] = [batch, nheads, state_rank, state_rank];
536 let shape_m1: [usize; 3] = [batch, nheads, state_rank];
537 let flat_len_m2 = batch * nheads * state_rank * state_rank;
538 let flat_len_m1 = batch * nheads * state_rank;
539
540 match MomentsBackward
542 .prepare::<C>([
543 xhat_bnlchp.node.clone(),
544 bhat_bnlchr.node.clone(),
545 da_bnlh.node.clone(),
546 rot.node.clone(),
547 initial_state_bhpr.node.clone(),
548 ])
549 .compute_bound()
550 .stateful()
551 {
552 OpsKind::Tracked(prep) => {
553 let (prim_m2, prim_m1) = B::mamba3_state_moments_phys(
554 xhat_bnlchp.primitive.clone(),
555 bhat_bnlchr.primitive.clone(),
556 da_bnlh.primitive.clone(),
557 rot.primitive.clone(),
558 initial_state_bhpr.primitive.clone(),
559 valid_len,
560 quaternion,
561 rope_dim,
562 rotate_pairwise,
563 );
564 let (prim_combined, _, _) =
565 crate::utils::combined_grad::flatten_pair::<B>(prim_m2, prim_m1);
566 let state = State {
567 xhat: xhat_bnlchp.primitive.clone(),
568 bhat: bhat_bnlchr.primitive.clone(),
569 da: da_bnlh.primitive.clone(),
570 rot: rot.primitive.clone(),
571 initial: initial_state_bhpr.primitive.clone(),
572 flat_len_m2,
573 flat_len_m1,
574 shape_m2,
575 shape_m1,
576 valid_len,
577 quaternion,
578 rope_dim,
579 rotate_pairwise,
580 };
581 let tracked_combined: FloatTensor<Autodiff<B, C>> =
582 prep.finish(state, prim_combined);
583 crate::utils::combined_grad::autodiff_unflatten_pair::<B, C, 4, 3>(
584 tracked_combined,
585 flat_len_m2,
586 flat_len_m1,
587 shape_m2,
588 shape_m1,
589 )
590 }
591 OpsKind::UnTracked(prep) => {
592 let (prim_m2, prim_m1) = B::mamba3_state_moments_phys(
593 xhat_bnlchp.primitive,
594 bhat_bnlchr.primitive,
595 da_bnlh.primitive,
596 rot.primitive,
597 initial_state_bhpr.primitive,
598 valid_len,
599 quaternion,
600 rope_dim,
601 rotate_pairwise,
602 );
603 let (prim_combined, _, _) =
604 crate::utils::combined_grad::flatten_pair::<B>(prim_m2, prim_m1);
605 let tracked_combined: FloatTensor<Autodiff<B, C>> = prep.finish(prim_combined);
606 crate::utils::combined_grad::autodiff_unflatten_pair::<B, C, 4, 3>(
607 tracked_combined,
608 flat_len_m2,
609 flat_len_m1,
610 shape_m2,
611 shape_m1,
612 )
613 }
614 }
615 }
616}