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(
131 &mut self,
132 dy: NodeId,
133 cos: NodeId,
134 sin: NodeId,
135 head_dim: usize,
136 n_rot: usize,
137 style: crate::op::RopeStyle,
138 ) -> NodeId {
139 let out_shape = self.shape(dy).clone();
140 self.push(
141 Op::RopeBackward {
142 head_dim,
143 n_rot,
144 style,
145 },
146 vec![dy, cos, sin],
147 out_shape,
148 None,
149 )
150 }
151
152 pub fn cumsum_backward(
153 &mut self,
154 dy: NodeId,
155 out_shape: Shape,
156 axis: i32,
157 exclusive: bool,
158 ) -> NodeId {
159 self.push(
160 Op::CumsumBackward { axis, exclusive },
161 vec![dy],
162 out_shape,
163 None,
164 )
165 }
166
167 pub fn gather_backward(
168 &mut self,
169 dy: NodeId,
170 indices: NodeId,
171 table_shape: Shape,
172 axis: i32,
173 ) -> NodeId {
174 self.push(
175 Op::GatherBackward { axis },
176 vec![dy, indices],
177 table_shape,
178 None,
179 )
180 }
181
182 pub fn group_norm_backward_input(
184 &mut self,
185 x: NodeId,
186 gamma: NodeId,
187 beta: NodeId,
188 dy: NodeId,
189 num_groups: usize,
190 eps: f32,
191 ) -> NodeId {
192 let x_shape = self.shape(x).clone();
193 self.push(
194 Op::GroupNormBackwardInput { num_groups, eps },
195 vec![x, gamma, beta, dy],
196 x_shape,
197 None,
198 )
199 }
200
201 pub fn group_norm_backward_gamma(
203 &mut self,
204 x: NodeId,
205 dy: NodeId,
206 gamma_shape: Shape,
207 num_groups: usize,
208 eps: f32,
209 ) -> NodeId {
210 self.push(
211 Op::GroupNormBackwardGamma { num_groups, eps },
212 vec![x, dy],
213 gamma_shape,
214 None,
215 )
216 }
217
218 pub fn group_norm_backward_beta(
220 &mut self,
221 x: NodeId,
222 dy: NodeId,
223 beta_shape: Shape,
224 num_groups: usize,
225 eps: f32,
226 ) -> NodeId {
227 self.push(
228 Op::GroupNormBackwardBeta { num_groups, eps },
229 vec![x, dy],
230 beta_shape,
231 None,
232 )
233 }
234
235 pub fn batch_norm_inference_backward_input(
237 &mut self,
238 x: NodeId,
239 gamma: NodeId,
240 mean: NodeId,
241 var: NodeId,
242 dy: NodeId,
243 eps: f32,
244 ) -> NodeId {
245 let x_shape = self.shape(x).clone();
246 debug_assert_eq!(self.shape(x), self.shape(dy));
247 self.push(
248 Op::BatchNormInferenceBackwardInput { eps },
249 vec![x, gamma, mean, var, dy],
250 x_shape,
251 None,
252 )
253 }
254
255 pub fn batch_norm_inference_backward_gamma(
257 &mut self,
258 x: NodeId,
259 mean: NodeId,
260 var: NodeId,
261 dy: NodeId,
262 gamma_shape: Shape,
263 eps: f32,
264 ) -> NodeId {
265 self.push(
266 Op::BatchNormInferenceBackwardGamma { eps },
267 vec![x, mean, var, dy],
268 gamma_shape,
269 None,
270 )
271 }
272
273 pub fn batch_norm_inference_backward_beta(&mut self, dy: NodeId, beta_shape: Shape) -> NodeId {
275 self.push(
276 Op::BatchNormInferenceBackwardBeta,
277 vec![dy],
278 beta_shape,
279 None,
280 )
281 }
282
283 pub fn layer_norm_backward_gamma(
287 &mut self,
288 x: NodeId,
289 dy: NodeId,
290 gamma_shape: Shape,
291 axis: i32,
292 eps: f32,
293 ) -> NodeId {
294 debug_assert_eq!(
295 self.shape(x),
296 self.shape(dy),
297 "layer_norm_backward_gamma: x and dy must match"
298 );
299 self.push(
300 Op::LayerNormBackwardGamma { axis, eps },
301 vec![x, dy],
302 gamma_shape,
303 None,
304 )
305 }
306
307 pub fn maxpool2d_backward(
311 &mut self,
312 x: NodeId,
313 dy: NodeId,
314 kernel_size: Vec<usize>,
315 stride: Vec<usize>,
316 padding: Vec<usize>,
317 ) -> NodeId {
318 let x_shape = self.shape(x).clone();
319 debug_assert_eq!(kernel_size.len(), 2, "maxpool2d_backward: 2-D only");
320 debug_assert_eq!(stride.len(), 2);
321 debug_assert_eq!(padding.len(), 2);
322 self.push(
323 Op::MaxPool2dBackward {
324 kernel_size,
325 stride,
326 padding,
327 },
328 vec![x, dy],
329 x_shape,
330 None,
331 )
332 }
333
334 pub fn conv2d_backward_input(
340 &mut self,
341 dy: NodeId,
342 w: NodeId,
343 x_shape: Shape,
344 kernel_size: Vec<usize>,
345 stride: Vec<usize>,
346 padding: Vec<usize>,
347 dilation: Vec<usize>,
348 groups: usize,
349 ) -> NodeId {
350 debug_assert_eq!(kernel_size.len(), 2);
351 debug_assert_eq!(stride.len(), 2);
352 debug_assert_eq!(padding.len(), 2);
353 debug_assert_eq!(dilation.len(), 2);
354 self.push(
355 Op::Conv2dBackwardInput {
356 kernel_size,
357 stride,
358 padding,
359 dilation,
360 groups,
361 },
362 vec![dy, w],
363 x_shape,
364 None,
365 )
366 }
367
368 pub fn conv2d_backward_weight(
371 &mut self,
372 x: NodeId,
373 dy: NodeId,
374 w_shape: Shape,
375 kernel_size: Vec<usize>,
376 stride: Vec<usize>,
377 padding: Vec<usize>,
378 dilation: Vec<usize>,
379 groups: usize,
380 ) -> NodeId {
381 debug_assert_eq!(kernel_size.len(), 2);
382 debug_assert_eq!(stride.len(), 2);
383 debug_assert_eq!(padding.len(), 2);
384 debug_assert_eq!(dilation.len(), 2);
385 self.push(
386 Op::Conv2dBackwardWeight {
387 kernel_size,
388 stride,
389 padding,
390 dilation,
391 groups,
392 },
393 vec![x, dy],
394 w_shape,
395 None,
396 )
397 }
398
399 pub fn maxpool3d_backward(
401 &mut self,
402 x: NodeId,
403 dy: NodeId,
404 kernel_size: Vec<usize>,
405 stride: Vec<usize>,
406 padding: Vec<usize>,
407 ) -> NodeId {
408 let x_shape = self.shape(x).clone();
409 debug_assert_eq!(x_shape.rank(), 5, "maxpool3d_backward: NCDHW only");
410 debug_assert_eq!(kernel_size.len(), 3, "maxpool3d_backward: 3-D only");
411 debug_assert_eq!(stride.len(), 3);
412 debug_assert_eq!(padding.len(), 3);
413 self.push(
414 Op::MaxPool3dBackward {
415 kernel_size,
416 stride,
417 padding,
418 },
419 vec![x, dy],
420 x_shape,
421 None,
422 )
423 }
424
425 pub fn conv3d_backward_input(
427 &mut self,
428 dy: NodeId,
429 w: NodeId,
430 x_shape: Shape,
431 kernel_size: Vec<usize>,
432 stride: Vec<usize>,
433 padding: Vec<usize>,
434 dilation: Vec<usize>,
435 groups: usize,
436 ) -> NodeId {
437 debug_assert_eq!(x_shape.rank(), 5, "conv3d_backward_input: NCDHW only");
438 debug_assert_eq!(kernel_size.len(), 3);
439 debug_assert_eq!(stride.len(), 3);
440 debug_assert_eq!(padding.len(), 3);
441 debug_assert_eq!(dilation.len(), 3);
442 self.push(
443 Op::Conv3dBackwardInput {
444 kernel_size,
445 stride,
446 padding,
447 dilation,
448 groups,
449 },
450 vec![dy, w],
451 x_shape,
452 None,
453 )
454 }
455
456 pub fn conv3d_backward_weight(
458 &mut self,
459 x: NodeId,
460 dy: NodeId,
461 w_shape: Shape,
462 kernel_size: Vec<usize>,
463 stride: Vec<usize>,
464 padding: Vec<usize>,
465 dilation: Vec<usize>,
466 groups: usize,
467 ) -> NodeId {
468 debug_assert_eq!(w_shape.rank(), 5, "conv3d_backward_weight: 5-D weight");
469 debug_assert_eq!(kernel_size.len(), 3);
470 debug_assert_eq!(stride.len(), 3);
471 debug_assert_eq!(padding.len(), 3);
472 debug_assert_eq!(dilation.len(), 3);
473 self.push(
474 Op::Conv3dBackwardWeight {
475 kernel_size,
476 stride,
477 padding,
478 dilation,
479 groups,
480 },
481 vec![x, dy],
482 w_shape,
483 None,
484 )
485 }
486
487 pub fn softmax_cross_entropy(&mut self, logits: NodeId, targets: NodeId) -> NodeId {
492 let logits_shape = self.shape(logits);
493 debug_assert_eq!(logits_shape.rank(), 2, "sce: logits must be 2-D [N, C]");
494 debug_assert_eq!(
495 self.shape(targets).dims(),
496 logits_shape.dims(),
497 "sce: targets must match logits shape [N, C]"
498 );
499 let n = logits_shape.dim(0);
500 let dtype = logits_shape.dtype();
501 let out_shape = Shape::from_dims(&[n], dtype);
502 self.push(
503 Op::SoftmaxCrossEntropy,
504 vec![logits, targets],
505 out_shape,
506 None,
507 )
508 }
509
510 pub fn softmax_cross_entropy_with_logits(&mut self, logits: NodeId, labels: NodeId) -> NodeId {
513 let logits_shape = self.shape(logits);
514 debug_assert_eq!(
515 logits_shape.rank(),
516 2,
517 "sce_with_logits: logits must be 2-D [N, C]"
518 );
519 let n = logits_shape.dim(0);
520 let dtype = logits_shape.dtype();
521 let out_shape = Shape::from_dims(&[n], dtype);
522 self.push(
523 Op::SoftmaxCrossEntropyWithLogits,
524 vec![logits, labels],
525 out_shape,
526 None,
527 )
528 }
529
530 pub fn softmax_cross_entropy_backward(
533 &mut self,
534 logits: NodeId,
535 labels: NodeId,
536 d_loss: NodeId,
537 ) -> NodeId {
538 let logits_shape = self.shape(logits).clone();
539 debug_assert_eq!(
540 logits_shape.rank(),
541 2,
542 "sce_backward: logits must be 2-D [N, C]"
543 );
544 self.push(
545 Op::SoftmaxCrossEntropyBackward,
546 vec![logits, labels, d_loss],
547 logits_shape,
548 None,
549 )
550 }
551
552 pub fn complex_norm_sq(&mut self, z: NodeId) -> NodeId {
557 let z_shape = self.shape(z).clone();
558 debug_assert_eq!(
559 z_shape.dtype(),
560 DType::C64,
561 "complex_norm_sq: input must be C64, got {:?}",
562 z_shape.dtype()
563 );
564 let out_shape = Shape::from_dims(z_shape.dims(), DType::F32);
565 self.push(Op::ComplexNormSq, vec![z], out_shape, None)
566 }
567
568 pub fn attention_backward(
572 &mut self,
573 wrt: AttentionBwdWrt,
574 q: NodeId,
575 k: NodeId,
576 v: NodeId,
577 dy: NodeId,
578 num_heads: usize,
579 head_dim: usize,
580 mask_kind: MaskKind,
581 mask: Option<NodeId>,
582 ) -> NodeId {
583 let out_shape = match wrt {
584 AttentionBwdWrt::Query => self.shape(q).clone(),
585 AttentionBwdWrt::Key => self.shape(k).clone(),
586 AttentionBwdWrt::Value => self.shape(v).clone(),
587 };
588 let mut inputs = vec![q, k, v, dy];
589 if matches!(mask_kind, MaskKind::Custom | MaskKind::Bias) {
590 inputs.push(mask.expect("attention_backward: mask required for Custom/Bias"));
591 }
592 self.push(
593 Op::AttentionBackward {
594 num_heads,
595 head_dim,
596 mask_kind,
597 wrt,
598 },
599 inputs,
600 out_shape,
601 None,
602 )
603 }
604
605 pub fn attention_backward_all(
607 &mut self,
608 q: NodeId,
609 k: NodeId,
610 v: NodeId,
611 dy: NodeId,
612 num_heads: usize,
613 head_dim: usize,
614 mask_kind: MaskKind,
615 mask: Option<NodeId>,
616 ) -> (NodeId, NodeId, NodeId) {
617 let dq = self.attention_backward(
618 AttentionBwdWrt::Query,
619 q,
620 k,
621 v,
622 dy,
623 num_heads,
624 head_dim,
625 mask_kind,
626 mask,
627 );
628 let dk = self.attention_backward(
629 AttentionBwdWrt::Key,
630 q,
631 k,
632 v,
633 dy,
634 num_heads,
635 head_dim,
636 mask_kind,
637 mask,
638 );
639 let dv = self.attention_backward(
640 AttentionBwdWrt::Value,
641 q,
642 k,
643 v,
644 dy,
645 num_heads,
646 head_dim,
647 mask_kind,
648 mask,
649 );
650 (dq, dk, dv)
651 }
652
653 pub fn complex_norm_sq_backward(&mut self, z: NodeId, g: NodeId) -> NodeId {
657 let z_shape = self.shape(z).clone();
658 debug_assert_eq!(z_shape.dtype(), DType::C64);
659 debug_assert_eq!(self.shape(g).dtype(), DType::F32);
660 debug_assert_eq!(
661 z_shape.dims(),
662 self.shape(g).dims(),
663 "complex_norm_sq_backward: z and g must share logical shape"
664 );
665 self.push(Op::ComplexNormSqBackward, vec![z, g], z_shape, None)
666 }
667
668 pub fn conjugate(&mut self, z: NodeId) -> NodeId {
672 let z_shape = self.shape(z).clone();
673 debug_assert_eq!(
674 z_shape.dtype(),
675 DType::C64,
676 "conjugate: input must be C64, got {:?}",
677 z_shape.dtype()
678 );
679 self.push(Op::Conjugate, vec![z], z_shape, None)
680 }
681
682 pub fn ada_layer_norm_backward(
685 &mut self,
686 x: NodeId,
687 scale: NodeId,
688 shift: NodeId,
689 dy: NodeId,
690 norm: crate::op::AdaNormKind,
691 eps: f32,
692 ) -> NodeId {
693 let x_shape = self.shape(x).clone();
694 let scale_shape = self.shape(scale).clone();
695 let nx = x_shape
696 .num_elements()
697 .expect("ada_layer_norm_backward: static x");
698 let ns = scale_shape
699 .num_elements()
700 .expect("ada_layer_norm_backward: static scale");
701 let out = Shape::new(&[nx + 2 * ns], x_shape.dtype());
702 self.push(
703 Op::AdaLayerNormBackward { norm, eps },
704 vec![x, scale, shift, dy],
705 out,
706 None,
707 )
708 }
709
710 pub fn gated_residual_backward(
713 &mut self,
714 x: NodeId,
715 y: NodeId,
716 gate: NodeId,
717 dy: NodeId,
718 ) -> NodeId {
719 let x_shape = self.shape(x).clone();
720 let gate_shape = self.shape(gate).clone();
721 let nx = x_shape
722 .num_elements()
723 .expect("gated_residual_backward: static x");
724 let ng = gate_shape
725 .num_elements()
726 .expect("gated_residual_backward: static gate");
727 let out = Shape::new(&[nx + nx + ng], x_shape.dtype());
728 self.push(Op::GatedResidualBackward, vec![x, y, gate, dy], out, None)
729 }
730}
731
732pub fn unpack_ada_layer_norm_backward(
734 g: &mut Graph,
735 packed: NodeId,
736 x_shape: &Shape,
737 scale_shape: &Shape,
738) -> (NodeId, NodeId, NodeId) {
739 let nx = x_shape.num_elements().expect("unpack ada: static x");
740 let ns = scale_shape
741 .num_elements()
742 .expect("unpack ada: static scale");
743 let dtype = x_shape.dtype();
744 let dx_flat = g.add_node(
745 Op::Narrow {
746 axis: 0,
747 start: 0,
748 len: nx,
749 },
750 vec![packed],
751 Shape::new(&[nx], dtype),
752 );
753 let dscale_flat = g.add_node(
754 Op::Narrow {
755 axis: 0,
756 start: nx,
757 len: ns,
758 },
759 vec![packed],
760 Shape::new(&[ns], dtype),
761 );
762 let dshift_flat = g.add_node(
763 Op::Narrow {
764 axis: 0,
765 start: nx + ns,
766 len: ns,
767 },
768 vec![packed],
769 Shape::new(&[ns], dtype),
770 );
771 let dx = reshape_like(g, dx_flat, x_shape);
772 let dscale = reshape_like(g, dscale_flat, scale_shape);
773 let dshift = reshape_like(g, dshift_flat, scale_shape);
774 (dx, dscale, dshift)
775}
776
777pub fn unpack_gated_residual_backward(
779 g: &mut Graph,
780 packed: NodeId,
781 x_shape: &Shape,
782 gate_shape: &Shape,
783) -> (NodeId, NodeId, NodeId) {
784 let nx = x_shape.num_elements().expect("unpack gate: static x");
785 let ng = gate_shape.num_elements().expect("unpack gate: static gate");
786 let dtype = x_shape.dtype();
787 let dx_flat = g.add_node(
788 Op::Narrow {
789 axis: 0,
790 start: 0,
791 len: nx,
792 },
793 vec![packed],
794 Shape::new(&[nx], dtype),
795 );
796 let dy_flat = g.add_node(
797 Op::Narrow {
798 axis: 0,
799 start: nx,
800 len: nx,
801 },
802 vec![packed],
803 Shape::new(&[nx], dtype),
804 );
805 let dgate_flat = g.add_node(
806 Op::Narrow {
807 axis: 0,
808 start: 2 * nx,
809 len: ng,
810 },
811 vec![packed],
812 Shape::new(&[ng], dtype),
813 );
814 let dx = reshape_like(g, dx_flat, x_shape);
815 let dy = reshape_like(g, dy_flat, x_shape);
816 let dgate = reshape_like(g, dgate_flat, gate_shape);
817 (dx, dy, dgate)
818}
819
820fn reshape_like(g: &mut Graph, flat: NodeId, shape: &Shape) -> NodeId {
821 let dims: Vec<i64> = shape
822 .dims()
823 .iter()
824 .map(|d| match d {
825 crate::shape::Dim::Static(n) => *n as i64,
826 _ => panic!("reshape_like: dynamic dim"),
827 })
828 .collect();
829 g.add_node(
830 Op::Reshape {
831 new_shape: dims.clone(),
832 },
833 vec![flat],
834 shape.clone(),
835 )
836}