1use crate::op::{AttentionBwdWrt, MaskKind};
19use crate::{DType, Graph, NodeId, Op, Shape};
20
21impl Graph {
22 pub fn relu_backward(&mut self, x: NodeId, dy: NodeId) -> NodeId {
24 let x_shape = self.shape(x).clone();
25 debug_assert_eq!(
26 self.shape(x),
27 self.shape(dy),
28 "relu_backward: x and dy must have identical shapes"
29 );
30 self.push(Op::ReluBackward, vec![x, dy], x_shape, None)
31 }
32
33 pub fn activation_backward(
37 &mut self,
38 kind: crate::op::Activation,
39 x: NodeId,
40 dy: NodeId,
41 ) -> NodeId {
42 let x_shape = self.shape(x).clone();
43 debug_assert_eq!(
44 self.shape(x),
45 self.shape(dy),
46 "activation_backward: x and dy must have identical shapes"
47 );
48 self.push(Op::ActivationBackward { kind }, vec![x, dy], x_shape, None)
49 }
50
51 pub fn layer_norm_backward_input(
54 &mut self,
55 x: NodeId,
56 gamma: NodeId,
57 dy: NodeId,
58 axis: i32,
59 eps: f32,
60 ) -> NodeId {
61 let x_shape = self.shape(x).clone();
62 debug_assert_eq!(
63 self.shape(x),
64 self.shape(dy),
65 "layer_norm_backward_input: x and dy must match"
66 );
67 self.push(
68 Op::LayerNormBackwardInput { axis, eps },
69 vec![x, gamma, dy],
70 x_shape,
71 None,
72 )
73 }
74
75 pub fn rms_norm_backward_input(
77 &mut self,
78 x: NodeId,
79 gamma: NodeId,
80 beta: NodeId,
81 dy: NodeId,
82 axis: i32,
83 eps: f32,
84 ) -> NodeId {
85 let x_shape = self.shape(x).clone();
86 self.push(
87 Op::RmsNormBackwardInput { axis, eps },
88 vec![x, gamma, beta, dy],
89 x_shape,
90 None,
91 )
92 }
93
94 pub fn rms_norm_backward_gamma(
95 &mut self,
96 x: NodeId,
97 gamma: NodeId,
98 beta: NodeId,
99 dy: NodeId,
100 axis: i32,
101 eps: f32,
102 ) -> NodeId {
103 self.push(
104 Op::RmsNormBackwardGamma { axis, eps },
105 vec![x, gamma, beta, dy],
106 self.shape(gamma).clone(),
107 None,
108 )
109 }
110
111 pub fn rms_norm_backward_beta(
112 &mut self,
113 x: NodeId,
114 gamma: NodeId,
115 beta: NodeId,
116 dy: NodeId,
117 axis: i32,
118 eps: f32,
119 ) -> NodeId {
120 self.push(
121 Op::RmsNormBackwardBeta { axis, eps },
122 vec![x, gamma, beta, dy],
123 self.shape(beta).clone(),
124 None,
125 )
126 }
127
128 pub fn rope_backward(
129 &mut self,
130 dy: NodeId,
131 cos: NodeId,
132 sin: NodeId,
133 head_dim: usize,
134 n_rot: usize,
135 ) -> NodeId {
136 let out_shape = self.shape(dy).clone();
137 self.push(
138 Op::RopeBackward { head_dim, n_rot },
139 vec![dy, cos, sin],
140 out_shape,
141 None,
142 )
143 }
144
145 pub fn cumsum_backward(
146 &mut self,
147 dy: NodeId,
148 out_shape: Shape,
149 axis: i32,
150 exclusive: bool,
151 ) -> NodeId {
152 self.push(
153 Op::CumsumBackward { axis, exclusive },
154 vec![dy],
155 out_shape,
156 None,
157 )
158 }
159
160 pub fn gather_backward(
161 &mut self,
162 dy: NodeId,
163 indices: NodeId,
164 table_shape: Shape,
165 axis: i32,
166 ) -> NodeId {
167 self.push(
168 Op::GatherBackward { axis },
169 vec![dy, indices],
170 table_shape,
171 None,
172 )
173 }
174
175 pub fn group_norm_backward_input(
177 &mut self,
178 x: NodeId,
179 gamma: NodeId,
180 beta: NodeId,
181 dy: NodeId,
182 num_groups: usize,
183 eps: f32,
184 ) -> NodeId {
185 let x_shape = self.shape(x).clone();
186 self.push(
187 Op::GroupNormBackwardInput { num_groups, eps },
188 vec![x, gamma, beta, dy],
189 x_shape,
190 None,
191 )
192 }
193
194 pub fn group_norm_backward_gamma(
196 &mut self,
197 x: NodeId,
198 dy: NodeId,
199 gamma_shape: Shape,
200 num_groups: usize,
201 eps: f32,
202 ) -> NodeId {
203 self.push(
204 Op::GroupNormBackwardGamma { num_groups, eps },
205 vec![x, dy],
206 gamma_shape,
207 None,
208 )
209 }
210
211 pub fn group_norm_backward_beta(
213 &mut self,
214 x: NodeId,
215 dy: NodeId,
216 beta_shape: Shape,
217 num_groups: usize,
218 eps: f32,
219 ) -> NodeId {
220 self.push(
221 Op::GroupNormBackwardBeta { num_groups, eps },
222 vec![x, dy],
223 beta_shape,
224 None,
225 )
226 }
227
228 pub fn batch_norm_inference_backward_input(
230 &mut self,
231 x: NodeId,
232 gamma: NodeId,
233 mean: NodeId,
234 var: NodeId,
235 dy: NodeId,
236 eps: f32,
237 ) -> NodeId {
238 let x_shape = self.shape(x).clone();
239 debug_assert_eq!(self.shape(x), self.shape(dy));
240 self.push(
241 Op::BatchNormInferenceBackwardInput { eps },
242 vec![x, gamma, mean, var, dy],
243 x_shape,
244 None,
245 )
246 }
247
248 pub fn batch_norm_inference_backward_gamma(
250 &mut self,
251 x: NodeId,
252 mean: NodeId,
253 var: NodeId,
254 dy: NodeId,
255 gamma_shape: Shape,
256 eps: f32,
257 ) -> NodeId {
258 self.push(
259 Op::BatchNormInferenceBackwardGamma { eps },
260 vec![x, mean, var, dy],
261 gamma_shape,
262 None,
263 )
264 }
265
266 pub fn batch_norm_inference_backward_beta(&mut self, dy: NodeId, beta_shape: Shape) -> NodeId {
268 self.push(
269 Op::BatchNormInferenceBackwardBeta,
270 vec![dy],
271 beta_shape,
272 None,
273 )
274 }
275
276 pub fn layer_norm_backward_gamma(
280 &mut self,
281 x: NodeId,
282 dy: NodeId,
283 gamma_shape: Shape,
284 axis: i32,
285 eps: f32,
286 ) -> NodeId {
287 debug_assert_eq!(
288 self.shape(x),
289 self.shape(dy),
290 "layer_norm_backward_gamma: x and dy must match"
291 );
292 self.push(
293 Op::LayerNormBackwardGamma { axis, eps },
294 vec![x, dy],
295 gamma_shape,
296 None,
297 )
298 }
299
300 pub fn maxpool2d_backward(
304 &mut self,
305 x: NodeId,
306 dy: NodeId,
307 kernel_size: Vec<usize>,
308 stride: Vec<usize>,
309 padding: Vec<usize>,
310 ) -> NodeId {
311 let x_shape = self.shape(x).clone();
312 debug_assert_eq!(kernel_size.len(), 2, "maxpool2d_backward: 2-D only");
313 debug_assert_eq!(stride.len(), 2);
314 debug_assert_eq!(padding.len(), 2);
315 self.push(
316 Op::MaxPool2dBackward {
317 kernel_size,
318 stride,
319 padding,
320 },
321 vec![x, dy],
322 x_shape,
323 None,
324 )
325 }
326
327 pub fn conv2d_backward_input(
333 &mut self,
334 dy: NodeId,
335 w: NodeId,
336 x_shape: Shape,
337 kernel_size: Vec<usize>,
338 stride: Vec<usize>,
339 padding: Vec<usize>,
340 dilation: Vec<usize>,
341 groups: usize,
342 ) -> NodeId {
343 debug_assert_eq!(kernel_size.len(), 2);
344 debug_assert_eq!(stride.len(), 2);
345 debug_assert_eq!(padding.len(), 2);
346 debug_assert_eq!(dilation.len(), 2);
347 self.push(
348 Op::Conv2dBackwardInput {
349 kernel_size,
350 stride,
351 padding,
352 dilation,
353 groups,
354 },
355 vec![dy, w],
356 x_shape,
357 None,
358 )
359 }
360
361 pub fn conv2d_backward_weight(
364 &mut self,
365 x: NodeId,
366 dy: NodeId,
367 w_shape: Shape,
368 kernel_size: Vec<usize>,
369 stride: Vec<usize>,
370 padding: Vec<usize>,
371 dilation: Vec<usize>,
372 groups: usize,
373 ) -> NodeId {
374 debug_assert_eq!(kernel_size.len(), 2);
375 debug_assert_eq!(stride.len(), 2);
376 debug_assert_eq!(padding.len(), 2);
377 debug_assert_eq!(dilation.len(), 2);
378 self.push(
379 Op::Conv2dBackwardWeight {
380 kernel_size,
381 stride,
382 padding,
383 dilation,
384 groups,
385 },
386 vec![x, dy],
387 w_shape,
388 None,
389 )
390 }
391
392 pub fn maxpool3d_backward(
394 &mut self,
395 x: NodeId,
396 dy: NodeId,
397 kernel_size: Vec<usize>,
398 stride: Vec<usize>,
399 padding: Vec<usize>,
400 ) -> NodeId {
401 let x_shape = self.shape(x).clone();
402 debug_assert_eq!(x_shape.rank(), 5, "maxpool3d_backward: NCDHW only");
403 debug_assert_eq!(kernel_size.len(), 3, "maxpool3d_backward: 3-D only");
404 debug_assert_eq!(stride.len(), 3);
405 debug_assert_eq!(padding.len(), 3);
406 self.push(
407 Op::MaxPool3dBackward {
408 kernel_size,
409 stride,
410 padding,
411 },
412 vec![x, dy],
413 x_shape,
414 None,
415 )
416 }
417
418 pub fn conv3d_backward_input(
420 &mut self,
421 dy: NodeId,
422 w: NodeId,
423 x_shape: Shape,
424 kernel_size: Vec<usize>,
425 stride: Vec<usize>,
426 padding: Vec<usize>,
427 dilation: Vec<usize>,
428 groups: usize,
429 ) -> NodeId {
430 debug_assert_eq!(x_shape.rank(), 5, "conv3d_backward_input: NCDHW only");
431 debug_assert_eq!(kernel_size.len(), 3);
432 debug_assert_eq!(stride.len(), 3);
433 debug_assert_eq!(padding.len(), 3);
434 debug_assert_eq!(dilation.len(), 3);
435 self.push(
436 Op::Conv3dBackwardInput {
437 kernel_size,
438 stride,
439 padding,
440 dilation,
441 groups,
442 },
443 vec![dy, w],
444 x_shape,
445 None,
446 )
447 }
448
449 pub fn conv3d_backward_weight(
451 &mut self,
452 x: NodeId,
453 dy: NodeId,
454 w_shape: Shape,
455 kernel_size: Vec<usize>,
456 stride: Vec<usize>,
457 padding: Vec<usize>,
458 dilation: Vec<usize>,
459 groups: usize,
460 ) -> NodeId {
461 debug_assert_eq!(w_shape.rank(), 5, "conv3d_backward_weight: 5-D weight");
462 debug_assert_eq!(kernel_size.len(), 3);
463 debug_assert_eq!(stride.len(), 3);
464 debug_assert_eq!(padding.len(), 3);
465 debug_assert_eq!(dilation.len(), 3);
466 self.push(
467 Op::Conv3dBackwardWeight {
468 kernel_size,
469 stride,
470 padding,
471 dilation,
472 groups,
473 },
474 vec![x, dy],
475 w_shape,
476 None,
477 )
478 }
479
480 pub fn softmax_cross_entropy(&mut self, logits: NodeId, targets: NodeId) -> NodeId {
485 let logits_shape = self.shape(logits);
486 debug_assert_eq!(logits_shape.rank(), 2, "sce: logits must be 2-D [N, C]");
487 debug_assert_eq!(
488 self.shape(targets).dims(),
489 logits_shape.dims(),
490 "sce: targets must match logits shape [N, C]"
491 );
492 let n = logits_shape.dim(0);
493 let dtype = logits_shape.dtype();
494 let out_shape = Shape::from_dims(&[n], dtype);
495 self.push(
496 Op::SoftmaxCrossEntropy,
497 vec![logits, targets],
498 out_shape,
499 None,
500 )
501 }
502
503 pub fn softmax_cross_entropy_with_logits(&mut self, logits: NodeId, labels: NodeId) -> NodeId {
506 let logits_shape = self.shape(logits);
507 debug_assert_eq!(
508 logits_shape.rank(),
509 2,
510 "sce_with_logits: logits must be 2-D [N, C]"
511 );
512 let n = logits_shape.dim(0);
513 let dtype = logits_shape.dtype();
514 let out_shape = Shape::from_dims(&[n], dtype);
515 self.push(
516 Op::SoftmaxCrossEntropyWithLogits,
517 vec![logits, labels],
518 out_shape,
519 None,
520 )
521 }
522
523 pub fn softmax_cross_entropy_backward(
526 &mut self,
527 logits: NodeId,
528 labels: NodeId,
529 d_loss: NodeId,
530 ) -> NodeId {
531 let logits_shape = self.shape(logits).clone();
532 debug_assert_eq!(
533 logits_shape.rank(),
534 2,
535 "sce_backward: logits must be 2-D [N, C]"
536 );
537 self.push(
538 Op::SoftmaxCrossEntropyBackward,
539 vec![logits, labels, d_loss],
540 logits_shape,
541 None,
542 )
543 }
544
545 pub fn complex_norm_sq(&mut self, z: NodeId) -> NodeId {
550 let z_shape = self.shape(z).clone();
551 debug_assert_eq!(
552 z_shape.dtype(),
553 DType::C64,
554 "complex_norm_sq: input must be C64, got {:?}",
555 z_shape.dtype()
556 );
557 let out_shape = Shape::from_dims(z_shape.dims(), DType::F32);
558 self.push(Op::ComplexNormSq, vec![z], out_shape, None)
559 }
560
561 pub fn attention_backward(
565 &mut self,
566 wrt: AttentionBwdWrt,
567 q: NodeId,
568 k: NodeId,
569 v: NodeId,
570 dy: NodeId,
571 num_heads: usize,
572 head_dim: usize,
573 mask_kind: MaskKind,
574 mask: Option<NodeId>,
575 ) -> NodeId {
576 let out_shape = match wrt {
577 AttentionBwdWrt::Query => self.shape(q).clone(),
578 AttentionBwdWrt::Key => self.shape(k).clone(),
579 AttentionBwdWrt::Value => self.shape(v).clone(),
580 };
581 let mut inputs = vec![q, k, v, dy];
582 if matches!(mask_kind, MaskKind::Custom | MaskKind::Bias) {
583 inputs.push(mask.expect("attention_backward: mask required for Custom/Bias"));
584 }
585 self.push(
586 Op::AttentionBackward {
587 num_heads,
588 head_dim,
589 mask_kind,
590 wrt,
591 },
592 inputs,
593 out_shape,
594 None,
595 )
596 }
597
598 pub fn attention_backward_all(
600 &mut self,
601 q: NodeId,
602 k: NodeId,
603 v: NodeId,
604 dy: NodeId,
605 num_heads: usize,
606 head_dim: usize,
607 mask_kind: MaskKind,
608 mask: Option<NodeId>,
609 ) -> (NodeId, NodeId, NodeId) {
610 let dq = self.attention_backward(
611 AttentionBwdWrt::Query,
612 q,
613 k,
614 v,
615 dy,
616 num_heads,
617 head_dim,
618 mask_kind,
619 mask,
620 );
621 let dk = self.attention_backward(
622 AttentionBwdWrt::Key,
623 q,
624 k,
625 v,
626 dy,
627 num_heads,
628 head_dim,
629 mask_kind,
630 mask,
631 );
632 let dv = self.attention_backward(
633 AttentionBwdWrt::Value,
634 q,
635 k,
636 v,
637 dy,
638 num_heads,
639 head_dim,
640 mask_kind,
641 mask,
642 );
643 (dq, dk, dv)
644 }
645
646 pub fn complex_norm_sq_backward(&mut self, z: NodeId, g: NodeId) -> NodeId {
650 let z_shape = self.shape(z).clone();
651 debug_assert_eq!(z_shape.dtype(), DType::C64);
652 debug_assert_eq!(self.shape(g).dtype(), DType::F32);
653 debug_assert_eq!(
654 z_shape.dims(),
655 self.shape(g).dims(),
656 "complex_norm_sq_backward: z and g must share logical shape"
657 );
658 self.push(Op::ComplexNormSqBackward, vec![z, g], z_shape, None)
659 }
660
661 pub fn conjugate(&mut self, z: NodeId) -> NodeId {
665 let z_shape = self.shape(z).clone();
666 debug_assert_eq!(
667 z_shape.dtype(),
668 DType::C64,
669 "conjugate: input must be C64, got {:?}",
670 z_shape.dtype()
671 );
672 self.push(Op::Conjugate, vec![z], z_shape, None)
673 }
674
675 pub fn ada_layer_norm_backward(
678 &mut self,
679 x: NodeId,
680 scale: NodeId,
681 shift: NodeId,
682 dy: NodeId,
683 norm: crate::op::AdaNormKind,
684 eps: f32,
685 ) -> NodeId {
686 let x_shape = self.shape(x).clone();
687 let scale_shape = self.shape(scale).clone();
688 let nx = x_shape
689 .num_elements()
690 .expect("ada_layer_norm_backward: static x");
691 let ns = scale_shape
692 .num_elements()
693 .expect("ada_layer_norm_backward: static scale");
694 let out = Shape::new(&[nx + 2 * ns], x_shape.dtype());
695 self.push(
696 Op::AdaLayerNormBackward { norm, eps },
697 vec![x, scale, shift, dy],
698 out,
699 None,
700 )
701 }
702
703 pub fn gated_residual_backward(
706 &mut self,
707 x: NodeId,
708 y: NodeId,
709 gate: NodeId,
710 dy: NodeId,
711 ) -> NodeId {
712 let x_shape = self.shape(x).clone();
713 let gate_shape = self.shape(gate).clone();
714 let nx = x_shape
715 .num_elements()
716 .expect("gated_residual_backward: static x");
717 let ng = gate_shape
718 .num_elements()
719 .expect("gated_residual_backward: static gate");
720 let out = Shape::new(&[nx + nx + ng], x_shape.dtype());
721 self.push(Op::GatedResidualBackward, vec![x, y, gate, dy], out, None)
722 }
723}
724
725pub fn unpack_ada_layer_norm_backward(
727 g: &mut Graph,
728 packed: NodeId,
729 x_shape: &Shape,
730 scale_shape: &Shape,
731) -> (NodeId, NodeId, NodeId) {
732 let nx = x_shape.num_elements().expect("unpack ada: static x");
733 let ns = scale_shape
734 .num_elements()
735 .expect("unpack ada: static scale");
736 let dtype = x_shape.dtype();
737 let dx_flat = g.add_node(
738 Op::Narrow {
739 axis: 0,
740 start: 0,
741 len: nx,
742 },
743 vec![packed],
744 Shape::new(&[nx], dtype),
745 );
746 let dscale_flat = g.add_node(
747 Op::Narrow {
748 axis: 0,
749 start: nx,
750 len: ns,
751 },
752 vec![packed],
753 Shape::new(&[ns], dtype),
754 );
755 let dshift_flat = g.add_node(
756 Op::Narrow {
757 axis: 0,
758 start: nx + ns,
759 len: ns,
760 },
761 vec![packed],
762 Shape::new(&[ns], dtype),
763 );
764 let dx = reshape_like(g, dx_flat, x_shape);
765 let dscale = reshape_like(g, dscale_flat, scale_shape);
766 let dshift = reshape_like(g, dshift_flat, scale_shape);
767 (dx, dscale, dshift)
768}
769
770pub fn unpack_gated_residual_backward(
772 g: &mut Graph,
773 packed: NodeId,
774 x_shape: &Shape,
775 gate_shape: &Shape,
776) -> (NodeId, NodeId, NodeId) {
777 let nx = x_shape.num_elements().expect("unpack gate: static x");
778 let ng = gate_shape.num_elements().expect("unpack gate: static gate");
779 let dtype = x_shape.dtype();
780 let dx_flat = g.add_node(
781 Op::Narrow {
782 axis: 0,
783 start: 0,
784 len: nx,
785 },
786 vec![packed],
787 Shape::new(&[nx], dtype),
788 );
789 let dy_flat = g.add_node(
790 Op::Narrow {
791 axis: 0,
792 start: nx,
793 len: nx,
794 },
795 vec![packed],
796 Shape::new(&[nx], dtype),
797 );
798 let dgate_flat = g.add_node(
799 Op::Narrow {
800 axis: 0,
801 start: 2 * nx,
802 len: ng,
803 },
804 vec![packed],
805 Shape::new(&[ng], dtype),
806 );
807 let dx = reshape_like(g, dx_flat, x_shape);
808 let dy = reshape_like(g, dy_flat, x_shape);
809 let dgate = reshape_like(g, dgate_flat, gate_shape);
810 (dx, dy, dgate)
811}
812
813fn reshape_like(g: &mut Graph, flat: NodeId, shape: &Shape) -> NodeId {
814 let dims: Vec<i64> = shape
815 .dims()
816 .iter()
817 .map(|d| match d {
818 crate::shape::Dim::Static(n) => *n as i64,
819 _ => panic!("reshape_like: dynamic dim"),
820 })
821 .collect();
822 g.add_node(
823 Op::Reshape {
824 new_shape: dims.clone(),
825 },
826 vec![flat],
827 shape.clone(),
828 )
829}