1use crate::dtype::scalar_constant_bytes;
26use crate::op::*;
27use crate::shape;
28use crate::{DType, Graph, NodeId, Op, Shape};
29
30pub trait GraphExt {
32 fn mm(&mut self, lhs: NodeId, rhs: NodeId) -> NodeId;
34
35 fn add(&mut self, lhs: NodeId, rhs: NodeId) -> NodeId;
37 fn sub(&mut self, lhs: NodeId, rhs: NodeId) -> NodeId;
38 fn mul(&mut self, lhs: NodeId, rhs: NodeId) -> NodeId;
39 fn div(&mut self, lhs: NodeId, rhs: NodeId) -> NodeId;
40
41 fn gelu(&mut self, x: NodeId) -> NodeId;
43 fn gelu_approx(&mut self, x: NodeId) -> NodeId;
48 fn silu(&mut self, x: NodeId) -> NodeId;
49 fn relu(&mut self, x: NodeId) -> NodeId;
50 fn exp(&mut self, x: NodeId) -> NodeId;
51 fn recip(&mut self, x: NodeId) -> NodeId;
53 fn sqrt(&mut self, x: NodeId) -> NodeId;
54 fn rsqrt(&mut self, x: NodeId) -> NodeId;
56 fn neg(&mut self, x: NodeId) -> NodeId;
57 fn tanh(&mut self, x: NodeId) -> NodeId;
58 fn sigmoid(&mut self, x: NodeId) -> NodeId;
61 fn log(&mut self, x: NodeId) -> NodeId;
63 fn abs(&mut self, x: NodeId) -> NodeId;
65
66 fn ln(&mut self, x: NodeId, gamma: NodeId, beta: NodeId, eps: f32) -> NodeId;
68 fn layer_norm2d(&mut self, x: NodeId, gamma: NodeId, beta: NodeId, eps: f32) -> NodeId;
69 fn group_norm(
70 &mut self,
71 x: NodeId,
72 gamma: NodeId,
73 beta: NodeId,
74 num_groups: usize,
75 eps: f32,
76 ) -> NodeId;
77 fn rms_norm(&mut self, x: NodeId, gamma: NodeId, beta: NodeId, eps: f32) -> NodeId;
78
79 fn ada_layer_norm(
84 &mut self,
85 x: NodeId,
86 scale: NodeId,
87 shift: NodeId,
88 norm: crate::op::AdaNormKind,
89 eps: f32,
90 ) -> NodeId;
91
92 fn gated_residual(&mut self, x: NodeId, y: NodeId, gate: NodeId) -> NodeId;
96
97 fn conv2d(
99 &mut self,
100 input: NodeId,
101 weight: NodeId,
102 kernel_size: [usize; 2],
103 stride: [usize; 2],
104 padding: [usize; 2],
105 dilation: [usize; 2],
106 groups: usize,
107 ) -> NodeId;
108 fn conv_transpose2d(
109 &mut self,
110 input: NodeId,
111 weight: NodeId,
112 kernel_size: [usize; 2],
113 stride: [usize; 2],
114 padding: [usize; 2],
115 dilation: [usize; 2],
116 output_padding: [usize; 2],
117 groups: usize,
118 ) -> NodeId;
119
120 fn pool2d(
123 &mut self,
124 input: NodeId,
125 kind: crate::op::ReduceOp,
126 kernel_size: [usize; 2],
127 stride: [usize; 2],
128 padding: [usize; 2],
129 ) -> NodeId;
130
131 fn sum(&mut self, x: NodeId, axes: Vec<usize>, keep_dim: bool) -> NodeId;
133 fn mean(&mut self, x: NodeId, axes: Vec<usize>, keep_dim: bool) -> NodeId;
134 fn sm(&mut self, x: NodeId, axis: i32) -> NodeId;
135
136 fn reshape_(&mut self, x: NodeId, new_shape: Vec<i64>) -> NodeId;
138 fn transpose_(&mut self, x: NodeId, perm: Vec<usize>) -> NodeId;
139 fn narrow_(&mut self, x: NodeId, axis: usize, start: usize, len: usize) -> NodeId;
140 fn concat_(&mut self, inputs: Vec<NodeId>, axis: usize) -> NodeId;
141 fn pad_(&mut self, x: NodeId, pads: Vec<[usize; 2]>, mode: PadMode) -> NodeId;
143 fn slice_(&mut self, x: NodeId, axis: usize, start: usize, len: usize, step: i64) -> NodeId;
146 fn roll_(&mut self, x: NodeId, shifts: Vec<i64>, dims: Vec<usize>) -> NodeId;
149 fn clamp_(&mut self, x: NodeId, min: f32, max: f32) -> NodeId;
151 fn tile_(&mut self, x: NodeId, reps: Vec<usize>) -> NodeId;
153 fn trilu_(&mut self, x: NodeId, upper: bool, diagonal: i64) -> NodeId;
155 fn cumprod_(&mut self, x: NodeId, axis: i32, exclusive: bool) -> NodeId;
157 fn cummax_(&mut self, x: NodeId, axis: i32, exclusive: bool) -> NodeId;
159 fn gather_(&mut self, table: NodeId, indices: NodeId, axis: usize) -> NodeId;
160
161 fn eq(&mut self, lhs: NodeId, rhs: NodeId) -> NodeId;
163 fn lt(&mut self, lhs: NodeId, rhs: NodeId) -> NodeId;
164
165 fn attention_(
167 &mut self,
168 q: NodeId,
169 k: NodeId,
170 v: NodeId,
171 mask: NodeId,
172 num_heads: usize,
173 head_dim: usize,
174 ) -> NodeId;
175
176 fn rope(&mut self, x: NodeId, cos: NodeId, sin: NodeId, head_dim: usize) -> NodeId;
178 fn rope_n(
180 &mut self,
181 x: NodeId,
182 cos: NodeId,
183 sin: NodeId,
184 head_dim: usize,
185 n_rot: usize,
186 ) -> NodeId;
187 fn rope_styled(
189 &mut self,
190 x: NodeId,
191 cos: NodeId,
192 sin: NodeId,
193 head_dim: usize,
194 style: crate::op::RopeStyle,
195 ) -> NodeId;
196 fn rope_n_styled(
198 &mut self,
199 x: NodeId,
200 cos: NodeId,
201 sin: NodeId,
202 head_dim: usize,
203 n_rot: usize,
204 style: crate::op::RopeStyle,
205 ) -> NodeId;
206
207 fn cast(&mut self, x: NodeId, to: DType) -> NodeId;
209
210 fn constant(&mut self, value: f64, dtype: DType) -> NodeId;
214
215 fn try_constant(&mut self, value: f64, dtype: DType) -> Result<NodeId, String>;
220
221 fn zeros(&mut self, dims: &[usize], dtype: DType) -> NodeId;
228
229 fn full(&mut self, dims: &[usize], value: f32, dtype: DType) -> NodeId;
232
233 fn batch_norm_training(&mut self, x: NodeId, gamma: NodeId, beta: NodeId, eps: f32) -> NodeId;
243
244 fn stop_gradient(&mut self, x: NodeId) -> NodeId;
250}
251
252fn reduce_axes(g: &mut Graph, x: NodeId, op: ReduceOp, axes: Vec<usize>, keep_dim: bool) -> NodeId {
261 let mut sorted = axes.clone();
262 sorted.sort_unstable();
263 let contiguous = sorted.windows(2).all(|w| w[1] == w[0] + 1);
264 if axes.len() <= 1 || contiguous {
265 let s = shape::reduce_shape(g.shape(x), &axes, keep_dim).expect("reduce shape inference");
266 return g.reduce(x, op, axes, keep_dim, s);
267 }
268 let mut cur = x;
271 for &ax in sorted.iter().rev() {
272 let s = shape::reduce_shape(g.shape(cur), &[ax], true).expect("reduce shape inference");
273 cur = g.reduce(cur, op, vec![ax], true, s);
274 }
275 if keep_dim {
276 cur
277 } else {
278 let s = shape::reduce_shape(g.shape(x), &axes, false).expect("reduce shape inference");
279 let dims: Vec<i64> = s.dims().iter().map(|d| d.unwrap_static() as i64).collect();
280 g.reshape(cur, dims, s)
281 }
282}
283
284impl GraphExt for Graph {
285 fn mm(&mut self, lhs: NodeId, rhs: NodeId) -> NodeId {
286 let s =
287 shape::matmul_shape(self.shape(lhs), self.shape(rhs)).expect("matmul shape inference");
288 self.matmul(lhs, rhs, s)
289 }
290
291 fn add(&mut self, lhs: NodeId, rhs: NodeId) -> NodeId {
292 let s = shape::binary_shape(self.shape(lhs), self.shape(rhs)).expect("add shape inference");
293 self.binary(BinaryOp::Add, lhs, rhs, s)
294 }
295
296 fn sub(&mut self, lhs: NodeId, rhs: NodeId) -> NodeId {
297 let s = shape::binary_shape(self.shape(lhs), self.shape(rhs)).expect("sub shape inference");
298 self.binary(BinaryOp::Sub, lhs, rhs, s)
299 }
300
301 fn mul(&mut self, lhs: NodeId, rhs: NodeId) -> NodeId {
302 let s = shape::binary_shape(self.shape(lhs), self.shape(rhs)).expect("mul shape inference");
303 self.binary(BinaryOp::Mul, lhs, rhs, s)
304 }
305
306 fn div(&mut self, lhs: NodeId, rhs: NodeId) -> NodeId {
307 let s = shape::binary_shape(self.shape(lhs), self.shape(rhs)).expect("div shape inference");
308 self.binary(BinaryOp::Div, lhs, rhs, s)
309 }
310
311 fn gelu(&mut self, x: NodeId) -> NodeId {
312 let s = shape::unary_shape(self.shape(x));
313 self.activation(Activation::Gelu, x, s)
314 }
315
316 fn gelu_approx(&mut self, x: NodeId) -> NodeId {
317 let s = shape::unary_shape(self.shape(x));
318 self.activation(Activation::GeluApprox, x, s)
319 }
320
321 fn silu(&mut self, x: NodeId) -> NodeId {
322 let s = shape::unary_shape(self.shape(x));
323 self.activation(Activation::Silu, x, s)
324 }
325
326 fn relu(&mut self, x: NodeId) -> NodeId {
327 let s = shape::unary_shape(self.shape(x));
328 self.activation(Activation::Relu, x, s)
329 }
330
331 fn exp(&mut self, x: NodeId) -> NodeId {
332 let s = shape::unary_shape(self.shape(x));
333 self.activation(Activation::Exp, x, s)
334 }
335
336 fn recip(&mut self, x: NodeId) -> NodeId {
337 let s = shape::unary_shape(self.shape(x));
338 self.activation(Activation::Recip, x, s)
339 }
340
341 fn sqrt(&mut self, x: NodeId) -> NodeId {
342 let s = shape::unary_shape(self.shape(x));
343 self.activation(Activation::Sqrt, x, s)
344 }
345
346 fn rsqrt(&mut self, x: NodeId) -> NodeId {
347 let s = shape::unary_shape(self.shape(x));
348 self.activation(Activation::Rsqrt, x, s)
349 }
350
351 fn neg(&mut self, x: NodeId) -> NodeId {
352 let s = shape::unary_shape(self.shape(x));
353 self.activation(Activation::Neg, x, s)
354 }
355
356 fn tanh(&mut self, x: NodeId) -> NodeId {
357 let s = shape::unary_shape(self.shape(x));
358 self.activation(Activation::Tanh, x, s)
359 }
360
361 fn sigmoid(&mut self, x: NodeId) -> NodeId {
362 let s = shape::unary_shape(self.shape(x));
363 self.activation(Activation::Sigmoid, x, s)
364 }
365
366 fn log(&mut self, x: NodeId) -> NodeId {
367 let s = shape::unary_shape(self.shape(x));
368 self.activation(Activation::Log, x, s)
369 }
370
371 fn abs(&mut self, x: NodeId) -> NodeId {
372 let s = shape::unary_shape(self.shape(x));
373 self.activation(Activation::Abs, x, s)
374 }
375
376 fn ln(&mut self, x: NodeId, gamma: NodeId, beta: NodeId, eps: f32) -> NodeId {
377 let s = shape::unary_shape(self.shape(x));
378 self.layer_norm(x, gamma, beta, -1, eps, s)
379 }
380
381 fn layer_norm2d(&mut self, x: NodeId, gamma: NodeId, beta: NodeId, eps: f32) -> NodeId {
382 Graph::layer_norm2d(self, x, gamma, beta, eps)
383 }
384
385 fn group_norm(
386 &mut self,
387 x: NodeId,
388 gamma: NodeId,
389 beta: NodeId,
390 num_groups: usize,
391 eps: f32,
392 ) -> NodeId {
393 Graph::group_norm(self, x, gamma, beta, num_groups, eps)
394 }
395
396 fn conv2d(
397 &mut self,
398 input: NodeId,
399 weight: NodeId,
400 kernel_size: [usize; 2],
401 stride: [usize; 2],
402 padding: [usize; 2],
403 dilation: [usize; 2],
404 groups: usize,
405 ) -> NodeId {
406 Graph::conv2d(
407 self,
408 input,
409 weight,
410 kernel_size,
411 stride,
412 padding,
413 dilation,
414 groups,
415 )
416 }
417
418 fn pool2d(
419 &mut self,
420 input: NodeId,
421 kind: crate::op::ReduceOp,
422 kernel_size: [usize; 2],
423 stride: [usize; 2],
424 padding: [usize; 2],
425 ) -> NodeId {
426 Graph::pool2d(self, input, kind, kernel_size, stride, padding)
427 }
428
429 fn conv_transpose2d(
430 &mut self,
431 input: NodeId,
432 weight: NodeId,
433 kernel_size: [usize; 2],
434 stride: [usize; 2],
435 padding: [usize; 2],
436 dilation: [usize; 2],
437 output_padding: [usize; 2],
438 groups: usize,
439 ) -> NodeId {
440 Graph::conv_transpose2d(
441 self,
442 input,
443 weight,
444 kernel_size,
445 stride,
446 padding,
447 dilation,
448 output_padding,
449 groups,
450 )
451 }
452
453 fn rms_norm(&mut self, x: NodeId, gamma: NodeId, beta: NodeId, eps: f32) -> NodeId {
454 let s = shape::unary_shape(self.shape(x));
455 self.add_node(Op::RmsNorm { axis: -1, eps }, vec![x, gamma, beta], s)
456 }
457
458 fn ada_layer_norm(
459 &mut self,
460 x: NodeId,
461 scale: NodeId,
462 shift: NodeId,
463 norm: crate::op::AdaNormKind,
464 eps: f32,
465 ) -> NodeId {
466 let s = shape::unary_shape(self.shape(x));
467 self.add_node(Op::AdaLayerNorm { norm, eps }, vec![x, scale, shift], s)
468 }
469
470 fn gated_residual(&mut self, x: NodeId, y: NodeId, gate: NodeId) -> NodeId {
471 let s = shape::unary_shape(self.shape(x));
472 self.add_node(Op::GatedResidual, vec![x, y, gate], s)
473 }
474
475 fn sum(&mut self, x: NodeId, axes: Vec<usize>, keep_dim: bool) -> NodeId {
476 reduce_axes(self, x, ReduceOp::Sum, axes, keep_dim)
477 }
478
479 fn mean(&mut self, x: NodeId, axes: Vec<usize>, keep_dim: bool) -> NodeId {
480 reduce_axes(self, x, ReduceOp::Mean, axes, keep_dim)
481 }
482
483 fn sm(&mut self, x: NodeId, axis: i32) -> NodeId {
484 let s = shape::softmax_shape(self.shape(x));
485 self.softmax(x, axis, s)
486 }
487
488 fn reshape_(&mut self, x: NodeId, new_shape: Vec<i64>) -> NodeId {
489 let s = shape::reshape_shape(self.shape(x), &new_shape).expect("reshape shape inference");
490 self.reshape(x, new_shape, s)
491 }
492
493 fn transpose_(&mut self, x: NodeId, perm: Vec<usize>) -> NodeId {
494 let s = shape::transpose_shape(self.shape(x), &perm).expect("transpose shape inference");
495 self.add_node(Op::Transpose { perm }, vec![x], s)
496 }
497
498 fn narrow_(&mut self, x: NodeId, axis: usize, start: usize, len: usize) -> NodeId {
499 let s = shape::narrow_shape(self.shape(x), axis, len).expect("narrow shape inference");
500 self.add_node(Op::Narrow { axis, start, len }, vec![x], s)
501 }
502
503 fn concat_(&mut self, inputs: Vec<NodeId>, axis: usize) -> NodeId {
504 let shapes: Vec<&Shape> = inputs.iter().map(|&id| self.shape(id)).collect();
505 let s = shape::concat_shape(&shapes, axis).expect("concat shape inference");
506 self.concat(inputs, axis, s)
507 }
508
509 fn pad_(&mut self, x: NodeId, pads: Vec<[usize; 2]>, mode: PadMode) -> NodeId {
510 let s = shape::pad_shape(self.shape(x), &pads).expect("pad shape inference");
511 self.add_node(Op::Pad { pads, mode }, vec![x], s)
512 }
513
514 fn roll_(&mut self, x: NodeId, shifts: Vec<i64>, dims: Vec<usize>) -> NodeId {
515 assert_eq!(
516 shifts.len(),
517 dims.len(),
518 "roll: shifts and dims must be the same length"
519 );
520 let s = shape::unary_shape(self.shape(x));
521 self.add_node(Op::Roll { shifts, dims }, vec![x], s)
522 }
523
524 fn slice_(&mut self, x: NodeId, axis: usize, start: usize, len: usize, step: i64) -> NodeId {
525 let s = shape::slice_shape(self.shape(x), axis, len).expect("slice shape inference");
526 self.add_node(
527 Op::Slice {
528 axis,
529 start,
530 len,
531 step,
532 },
533 vec![x],
534 s,
535 )
536 }
537
538 fn clamp_(&mut self, x: NodeId, min: f32, max: f32) -> NodeId {
539 let s = shape::unary_shape(self.shape(x));
540 self.add_node(Op::Clamp { min, max }, vec![x], s)
541 }
542
543 fn tile_(&mut self, x: NodeId, reps: Vec<usize>) -> NodeId {
544 let s = shape::tile_shape(self.shape(x), &reps).expect("tile shape inference");
545 self.add_node(Op::Tile { reps }, vec![x], s)
546 }
547
548 fn trilu_(&mut self, x: NodeId, upper: bool, diagonal: i64) -> NodeId {
549 let s = shape::unary_shape(self.shape(x));
550 self.add_node(Op::Trilu { upper, diagonal }, vec![x], s)
551 }
552
553 fn cumprod_(&mut self, x: NodeId, axis: i32, exclusive: bool) -> NodeId {
554 let s = shape::unary_shape(self.shape(x));
555 self.add_node(Op::CumProd { axis, exclusive }, vec![x], s)
556 }
557
558 fn cummax_(&mut self, x: NodeId, axis: i32, exclusive: bool) -> NodeId {
559 let s = shape::unary_shape(self.shape(x));
560 self.add_node(Op::CumMax { axis, exclusive }, vec![x], s)
561 }
562
563 fn gather_(&mut self, table: NodeId, indices: NodeId, axis: usize) -> NodeId {
564 let s = shape::gather_shape(self.shape(table), self.shape(indices), axis)
565 .expect("gather shape inference");
566 self.gather(table, indices, axis, s)
567 }
568
569 fn eq(&mut self, lhs: NodeId, rhs: NodeId) -> NodeId {
570 let s = shape::compare_shape(self.shape(lhs), self.shape(rhs))
571 .expect("compare shape inference");
572 self.add_node(Op::Compare(CmpOp::Eq), vec![lhs, rhs], s)
573 }
574
575 fn lt(&mut self, lhs: NodeId, rhs: NodeId) -> NodeId {
576 let s = shape::compare_shape(self.shape(lhs), self.shape(rhs))
577 .expect("compare shape inference");
578 self.add_node(Op::Compare(CmpOp::Lt), vec![lhs, rhs], s)
579 }
580
581 fn attention_(
582 &mut self,
583 q: NodeId,
584 k: NodeId,
585 v: NodeId,
586 mask: NodeId,
587 num_heads: usize,
588 head_dim: usize,
589 ) -> NodeId {
590 let s = shape::attention_shape(self.shape(q));
591 self.attention(q, k, v, mask, num_heads, head_dim, s)
592 }
593
594 fn rope(&mut self, x: NodeId, cos: NodeId, sin: NodeId, head_dim: usize) -> NodeId {
595 self.rope_n(x, cos, sin, head_dim, head_dim)
596 }
597
598 fn rope_n(
599 &mut self,
600 x: NodeId,
601 cos: NodeId,
602 sin: NodeId,
603 head_dim: usize,
604 n_rot: usize,
605 ) -> NodeId {
606 self.rope_n_styled(x, cos, sin, head_dim, n_rot, crate::op::RopeStyle::NeoX)
607 }
608
609 fn rope_styled(
610 &mut self,
611 x: NodeId,
612 cos: NodeId,
613 sin: NodeId,
614 head_dim: usize,
615 style: crate::op::RopeStyle,
616 ) -> NodeId {
617 self.rope_n_styled(x, cos, sin, head_dim, head_dim, style)
618 }
619
620 fn rope_n_styled(
621 &mut self,
622 x: NodeId,
623 cos: NodeId,
624 sin: NodeId,
625 head_dim: usize,
626 n_rot: usize,
627 style: crate::op::RopeStyle,
628 ) -> NodeId {
629 assert!(
630 n_rot <= head_dim && n_rot.is_multiple_of(2),
631 "rope_n: n_rot={n_rot} must be even and <= head_dim={head_dim}"
632 );
633 let s = shape::unary_shape(self.shape(x));
634 self.add_node(
635 Op::Rope {
636 head_dim,
637 n_rot,
638 style,
639 },
640 vec![x, cos, sin],
641 s,
642 )
643 }
644
645 fn cast(&mut self, x: NodeId, to: DType) -> NodeId {
646 let s = shape::cast_shape(self.shape(x), to);
647 self.add_node(Op::Cast { to }, vec![x], s)
648 }
649
650 fn try_constant(&mut self, value: f64, dtype: DType) -> Result<NodeId, String> {
651 if matches!(dtype, DType::F16 | DType::BF16) {
652 let f32_id = self.try_constant(value, DType::F32)?;
653 return Ok(self.cast(f32_id, dtype));
654 }
655 let data = scalar_constant_bytes(value, dtype)?;
656 Ok(self.add_node(Op::Constant { data }, vec![], Shape::scalar(dtype)))
657 }
658 fn zeros(&mut self, dims: &[usize], dtype: DType) -> NodeId {
659 let numel: usize = dims.iter().product();
660 let data = vec![0u8; numel * dtype.size_bytes()];
661 self.add_node(Op::Constant { data }, vec![], Shape::new(dims, dtype))
662 }
663 fn full(&mut self, dims: &[usize], value: f32, dtype: DType) -> NodeId {
664 let numel: usize = dims.iter().product();
665 let elem = scalar_constant_bytes(value as f64, dtype).expect("full: encode value");
666 let data: Vec<u8> = elem
667 .iter()
668 .cloned()
669 .cycle()
670 .take(numel * elem.len())
671 .collect();
672 self.add_node(Op::Constant { data }, vec![], Shape::new(dims, dtype))
673 }
674 fn batch_norm_training(&mut self, x: NodeId, gamma: NodeId, beta: NodeId, eps: f32) -> NodeId {
675 let dims: Vec<usize> = self
676 .shape(x)
677 .dims()
678 .iter()
679 .map(|d| d.unwrap_static())
680 .collect();
681 assert_eq!(dims.len(), 4, "batch_norm_training expects [N,C,H,W]");
682 let c = dims[1];
683 let dt = self.shape(x).dtype();
684 let mu = self.mean(x, vec![0, 2, 3], true);
690 let xc = self.sub(x, mu);
691 let sq = self.mul(xc, xc);
692 let var = self.mean(sq, vec![0, 2, 3], true);
693 let eps_c = self.constant(eps as f64, dt);
694 let var_eps = self.add(var, eps_c);
695 let std = self.sqrt(var_eps);
696 let xhat = self.div(xc, std);
697 let gr = self.reshape_(gamma, vec![1, c as i64, 1, 1]);
698 let br = self.reshape_(beta, vec![1, c as i64, 1, 1]);
699 let scaled = self.mul(xhat, gr);
700 self.add(scaled, br)
701 }
702
703 fn constant(&mut self, value: f64, dtype: DType) -> NodeId {
704 self.try_constant(value, dtype)
705 .expect("scalar constant encoding")
706 }
707
708 fn stop_gradient(&mut self, x: NodeId) -> NodeId {
709 let s = shape::unary_shape(self.shape(x));
710 self.add_node(Op::StopGradient, vec![x], s)
711 }
712}
713
714#[cfg(test)]
715mod tests {
716 use super::*;
717
718 #[test]
719 fn inferred_conv2d_and_conv_transpose2d() {
720 let mut g = Graph::new("conv");
721 let f = DType::F32;
722 let x = g.input("x", Shape::new(&[1, 4, 8, 8], f));
723 let w = g.param("w", Shape::new(&[8, 2, 3, 3], f));
724 let y = g.conv2d(x, w, [3, 3], [1, 1], [1, 1], [1, 1], 2);
725 assert_eq!(g.shape(y), &Shape::new(&[1, 8, 8, 8], f));
726
727 let wt = g.param("wt", Shape::new(&[4, 8, 2, 2], f));
728 let z = g.conv_transpose2d(x, wt, [2, 2], [2, 2], [0, 0], [1, 1], [0, 0], 1);
729 assert_eq!(g.shape(z), &Shape::new(&[1, 8, 16, 16], f));
730 }
731
732 #[test]
733 fn batch_norm_training_and_full_shapes() {
734 let mut g = Graph::new("bn");
735 let f = DType::F32;
736 let x = g.input("x", Shape::new(&[2, 3, 4, 5], f));
737 let gamma = g.param("g", Shape::new(&[3], f));
738 let beta = g.param("b", Shape::new(&[3], f));
739 let y = g.batch_norm_training(x, gamma, beta, 1e-5); assert_eq!(g.shape(y), &Shape::new(&[2, 3, 4, 5], f)); let ones = g.full(&[3], 1.0, f);
742 assert_eq!(g.shape(ones), &Shape::new(&[3], f));
743 assert!(matches!(g.node(ones).op, Op::Constant { .. }));
744 }
745
746 #[test]
747 fn zeros_tensor_shape_and_no_params() {
748 let mut g = Graph::new("zeros");
749 let f = DType::F32;
750 let z = g.zeros(&[2, 3, 1, 4], f);
751 assert_eq!(g.shape(z), &Shape::new(&[2, 3, 1, 4], f));
752 assert!(matches!(g.node(z).op, Op::Constant { .. }));
754 }
755
756 #[test]
757 fn inferred_layer_norm2d() {
758 let mut g = Graph::new("ln2d");
759 let f = DType::F32;
760 let x = g.input("x", Shape::new(&[1, 4, 8, 8], f));
761 let gamma = g.param("g", Shape::new(&[4], f));
762 let beta = g.param("b", Shape::new(&[4], f));
763 let y = g.layer_norm2d(x, gamma, beta, 1e-6);
764 assert_eq!(g.shape(y), &Shape::new(&[1, 4, 8, 8], f));
765 }
766
767 #[test]
768 fn inferred_matmul_bias_gelu() {
769 let mut g = Graph::new("test");
770 let x = g.input("x", Shape::new(&[4, 15, 384], DType::F32));
771 let w = g.param("w", Shape::new(&[384, 1536], DType::F32));
772 let b = g.param("b", Shape::new(&[1536], DType::F32));
773
774 let mm = g.mm(x, w);
776 let add = g.add(mm, b);
777 let out = g.gelu(add);
778 g.set_outputs(vec![out]);
779
780 assert_eq!(g.shape(mm), &Shape::new(&[4, 15, 1536], DType::F32));
781 assert_eq!(g.shape(add), &Shape::new(&[4, 15, 1536], DType::F32));
782 assert_eq!(g.shape(out), &Shape::new(&[4, 15, 1536], DType::F32));
783 }
784
785 #[test]
786 fn inferred_bert_ffn() {
787 let mut g = Graph::new("bert_ffn");
788 let f = DType::F32;
789 let h = 384;
790 let int = 1536;
791
792 let x = g.input("x", Shape::new(&[4, 15, h], f));
793 let int_w = g.param("int.w", Shape::new(&[h, int], f));
794 let int_b = g.param("int.b", Shape::new(&[int], f));
795 let out_w = g.param("out.w", Shape::new(&[int, h], f));
796 let out_b = g.param("out.b", Shape::new(&[h], f));
797 let gamma = g.param("g", Shape::new(&[h], f));
798 let beta = g.param("b", Shape::new(&[h], f));
799
800 let mm1 = g.mm(x, int_w);
801 let a1 = g.add(mm1, int_b);
802 let ffn = g.gelu(a1);
803 let mm2 = g.mm(ffn, out_w);
804 let out = g.add(mm2, out_b);
805 let res = g.add(out, x);
806 let normed = g.ln(res, gamma, beta, 1e-12);
807 g.set_outputs(vec![normed]);
808
809 assert_eq!(g.shape(ffn), &Shape::new(&[4, 15, int], f));
810 assert_eq!(g.shape(out), &Shape::new(&[4, 15, h], f));
811 assert_eq!(g.shape(normed), &Shape::new(&[4, 15, h], f));
812 }
813
814 #[test]
815 fn inferred_gather_reshape() {
816 let mut g = Graph::new("test");
817 let table = g.param("emb", Shape::new(&[30522, 384], DType::F32));
818 let ids = g.input("ids", Shape::new(&[4, 15], DType::I64));
819
820 let gathered = g.gather_(table, ids, 0);
821 assert_eq!(g.shape(gathered), &Shape::new(&[4, 15, 384], DType::F32));
822
823 let reshaped = g.reshape_(gathered, vec![60, 384]);
824 assert_eq!(g.shape(reshaped), &Shape::new(&[60, 384], DType::F32));
825
826 let transposed = g.transpose_(reshaped, vec![1, 0]);
827 assert_eq!(g.shape(transposed), &Shape::new(&[384, 60], DType::F32));
828 }
829
830 #[test]
831 fn inferred_constant_broadcasts() {
832 let mut g = Graph::new("const");
833 let x = g.input("x", Shape::new(&[2, 3], DType::F32));
834 let c = g.constant(2.0, DType::F32);
835 assert_eq!(g.shape(c), &Shape::scalar(DType::F32));
836 let y = g.mul(x, c);
837 assert_eq!(g.shape(y), &Shape::new(&[2, 3], DType::F32));
838 }
839
840 #[test]
841 fn inferred_constant_f16_via_cast() {
842 let mut g = Graph::new("const_f16");
843 let c = g.constant(1.5, DType::F16);
844 assert_eq!(g.shape(c), &Shape::scalar(DType::F16));
845 let x = g.input("x", Shape::new(&[2], DType::F16));
846 let y = g.add(x, c);
847 assert_eq!(g.shape(y), &Shape::new(&[2], DType::F16));
848 }
849
850 #[test]
851 fn inferred_constant_arithmetic_chain() {
852 let mut g = Graph::new("const_chain");
853 let x = g.input("x", Shape::new(&[4], DType::F32));
854 let one = g.constant(1.0, DType::F32);
855 let two = g.constant(2.0, DType::F32);
856 let sum = g.add(x, one);
857 let y = g.div(sum, two);
858 assert_eq!(g.shape(y), &Shape::new(&[4], DType::F32));
859 g.set_outputs(vec![y]);
860 }
861
862 #[test]
863 fn try_constant_rejects_out_of_range() {
864 let mut g = Graph::new("try_const");
865 let err = g.try_constant(128.0, DType::I8).unwrap_err();
866 assert!(err.contains("out of range"));
867 }
868
869 #[test]
870 fn try_constant_f16_via_cast() {
871 let mut g = Graph::new("try_const_f16");
872 let c = g.try_constant(1.5, DType::F16).unwrap();
873 assert_eq!(g.shape(c), &Shape::scalar(DType::F16));
874 }
875
876 #[test]
877 fn inferred_reduce_softmax() {
878 let mut g = Graph::new("test");
879 let x = g.input("x", Shape::new(&[4, 15, 384], DType::F32));
880
881 let s = g.sm(x, -1);
882 assert_eq!(g.shape(s), &Shape::new(&[4, 15, 384], DType::F32));
883
884 let m = g.mean(x, vec![2], false);
885 assert_eq!(g.shape(m), &Shape::new(&[4, 15], DType::F32));
886
887 let mk = g.mean(x, vec![2], true);
888 assert_eq!(g.shape(mk), &Shape::new(&[4, 15, 1], DType::F32));
889 }
890}