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_at(self.shape(x), axis, start, len)
500 .expect("narrow shape inference");
501 self.add_node(Op::Narrow { axis, start, len }, vec![x], s)
502 }
503
504 fn concat_(&mut self, inputs: Vec<NodeId>, axis: usize) -> NodeId {
505 let shapes: Vec<&Shape> = inputs.iter().map(|&id| self.shape(id)).collect();
506 let s = shape::concat_shape(&shapes, axis).expect("concat shape inference");
507 self.concat(inputs, axis, s)
508 }
509
510 fn pad_(&mut self, x: NodeId, pads: Vec<[usize; 2]>, mode: PadMode) -> NodeId {
511 let s = shape::pad_shape(self.shape(x), &pads).expect("pad shape inference");
512 self.add_node(Op::Pad { pads, mode }, vec![x], s)
513 }
514
515 fn roll_(&mut self, x: NodeId, shifts: Vec<i64>, dims: Vec<usize>) -> NodeId {
516 assert_eq!(
517 shifts.len(),
518 dims.len(),
519 "roll: shifts and dims must be the same length"
520 );
521 let s = shape::unary_shape(self.shape(x));
522 self.add_node(Op::Roll { shifts, dims }, vec![x], s)
523 }
524
525 fn slice_(&mut self, x: NodeId, axis: usize, start: usize, len: usize, step: i64) -> NodeId {
526 let s = shape::slice_shape(self.shape(x), axis, len).expect("slice shape inference");
527 self.add_node(
528 Op::Slice {
529 axis,
530 start,
531 len,
532 step,
533 },
534 vec![x],
535 s,
536 )
537 }
538
539 fn clamp_(&mut self, x: NodeId, min: f32, max: f32) -> NodeId {
540 let s = shape::unary_shape(self.shape(x));
541 self.add_node(Op::Clamp { min, max }, vec![x], s)
542 }
543
544 fn tile_(&mut self, x: NodeId, reps: Vec<usize>) -> NodeId {
545 let s = shape::tile_shape(self.shape(x), &reps).expect("tile shape inference");
546 self.add_node(Op::Tile { reps }, vec![x], s)
547 }
548
549 fn trilu_(&mut self, x: NodeId, upper: bool, diagonal: i64) -> NodeId {
550 let s = shape::unary_shape(self.shape(x));
551 self.add_node(Op::Trilu { upper, diagonal }, vec![x], s)
552 }
553
554 fn cumprod_(&mut self, x: NodeId, axis: i32, exclusive: bool) -> NodeId {
555 let s = shape::unary_shape(self.shape(x));
556 self.add_node(Op::CumProd { axis, exclusive }, vec![x], s)
557 }
558
559 fn cummax_(&mut self, x: NodeId, axis: i32, exclusive: bool) -> NodeId {
560 let s = shape::unary_shape(self.shape(x));
561 self.add_node(Op::CumMax { axis, exclusive }, vec![x], s)
562 }
563
564 fn gather_(&mut self, table: NodeId, indices: NodeId, axis: usize) -> NodeId {
565 let s = shape::gather_shape(self.shape(table), self.shape(indices), axis)
566 .expect("gather shape inference");
567 self.gather(table, indices, axis, s)
568 }
569
570 fn eq(&mut self, lhs: NodeId, rhs: NodeId) -> NodeId {
571 let s = shape::compare_shape(self.shape(lhs), self.shape(rhs))
572 .expect("compare shape inference");
573 self.add_node(Op::Compare(CmpOp::Eq), vec![lhs, rhs], s)
574 }
575
576 fn lt(&mut self, lhs: NodeId, rhs: NodeId) -> NodeId {
577 let s = shape::compare_shape(self.shape(lhs), self.shape(rhs))
578 .expect("compare shape inference");
579 self.add_node(Op::Compare(CmpOp::Lt), vec![lhs, rhs], s)
580 }
581
582 fn attention_(
583 &mut self,
584 q: NodeId,
585 k: NodeId,
586 v: NodeId,
587 mask: NodeId,
588 num_heads: usize,
589 head_dim: usize,
590 ) -> NodeId {
591 let s = shape::attention_shape(self.shape(q));
592 self.attention(q, k, v, mask, num_heads, head_dim, s)
593 }
594
595 fn rope(&mut self, x: NodeId, cos: NodeId, sin: NodeId, head_dim: usize) -> NodeId {
596 self.rope_n(x, cos, sin, head_dim, head_dim)
597 }
598
599 fn rope_n(
600 &mut self,
601 x: NodeId,
602 cos: NodeId,
603 sin: NodeId,
604 head_dim: usize,
605 n_rot: usize,
606 ) -> NodeId {
607 self.rope_n_styled(x, cos, sin, head_dim, n_rot, crate::op::RopeStyle::NeoX)
608 }
609
610 fn rope_styled(
611 &mut self,
612 x: NodeId,
613 cos: NodeId,
614 sin: NodeId,
615 head_dim: usize,
616 style: crate::op::RopeStyle,
617 ) -> NodeId {
618 self.rope_n_styled(x, cos, sin, head_dim, head_dim, style)
619 }
620
621 fn rope_n_styled(
622 &mut self,
623 x: NodeId,
624 cos: NodeId,
625 sin: NodeId,
626 head_dim: usize,
627 n_rot: usize,
628 style: crate::op::RopeStyle,
629 ) -> NodeId {
630 assert!(
631 n_rot <= head_dim && n_rot.is_multiple_of(2),
632 "rope_n: n_rot={n_rot} must be even and <= head_dim={head_dim}"
633 );
634 let s = shape::unary_shape(self.shape(x));
635 self.add_node(
636 Op::Rope {
637 head_dim,
638 n_rot,
639 style,
640 },
641 vec![x, cos, sin],
642 s,
643 )
644 }
645
646 fn cast(&mut self, x: NodeId, to: DType) -> NodeId {
647 let s = shape::cast_shape(self.shape(x), to);
648 self.add_node(Op::Cast { to }, vec![x], s)
649 }
650
651 fn try_constant(&mut self, value: f64, dtype: DType) -> Result<NodeId, String> {
652 if matches!(dtype, DType::F16 | DType::BF16) {
653 let f32_id = self.try_constant(value, DType::F32)?;
654 return Ok(self.cast(f32_id, dtype));
655 }
656 let data = scalar_constant_bytes(value, dtype)?;
657 Ok(self.add_node(Op::Constant { data }, vec![], Shape::scalar(dtype)))
658 }
659 fn zeros(&mut self, dims: &[usize], dtype: DType) -> NodeId {
660 let numel: usize = dims.iter().product();
661 let data = vec![0u8; numel * dtype.size_bytes()];
662 self.add_node(Op::Constant { data }, vec![], Shape::new(dims, dtype))
663 }
664 fn full(&mut self, dims: &[usize], value: f32, dtype: DType) -> NodeId {
665 let numel: usize = dims.iter().product();
666 let elem = scalar_constant_bytes(value as f64, dtype).expect("full: encode value");
667 let data: Vec<u8> = elem
668 .iter()
669 .cloned()
670 .cycle()
671 .take(numel * elem.len())
672 .collect();
673 self.add_node(Op::Constant { data }, vec![], Shape::new(dims, dtype))
674 }
675 fn batch_norm_training(&mut self, x: NodeId, gamma: NodeId, beta: NodeId, eps: f32) -> NodeId {
676 let dims: Vec<usize> = self
677 .shape(x)
678 .dims()
679 .iter()
680 .map(|d| d.unwrap_static())
681 .collect();
682 assert_eq!(dims.len(), 4, "batch_norm_training expects [N,C,H,W]");
683 let c = dims[1];
684 let dt = self.shape(x).dtype();
685 let mu = self.mean(x, vec![0, 2, 3], true);
691 let xc = self.sub(x, mu);
692 let sq = self.mul(xc, xc);
693 let var = self.mean(sq, vec![0, 2, 3], true);
694 let eps_c = self.constant(eps as f64, dt);
695 let var_eps = self.add(var, eps_c);
696 let std = self.sqrt(var_eps);
697 let xhat = self.div(xc, std);
698 let gr = self.reshape_(gamma, vec![1, c as i64, 1, 1]);
699 let br = self.reshape_(beta, vec![1, c as i64, 1, 1]);
700 let scaled = self.mul(xhat, gr);
701 self.add(scaled, br)
702 }
703
704 fn constant(&mut self, value: f64, dtype: DType) -> NodeId {
705 self.try_constant(value, dtype)
706 .expect("scalar constant encoding")
707 }
708
709 fn stop_gradient(&mut self, x: NodeId) -> NodeId {
710 let s = shape::unary_shape(self.shape(x));
711 self.add_node(Op::StopGradient, vec![x], s)
712 }
713}
714
715#[cfg(test)]
716mod tests {
717 use super::*;
718
719 #[test]
720 fn inferred_conv2d_and_conv_transpose2d() {
721 let mut g = Graph::new("conv");
722 let f = DType::F32;
723 let x = g.input("x", Shape::new(&[1, 4, 8, 8], f));
724 let w = g.param("w", Shape::new(&[8, 2, 3, 3], f));
725 let y = g.conv2d(x, w, [3, 3], [1, 1], [1, 1], [1, 1], 2);
726 assert_eq!(g.shape(y), &Shape::new(&[1, 8, 8, 8], f));
727
728 let wt = g.param("wt", Shape::new(&[4, 8, 2, 2], f));
729 let z = g.conv_transpose2d(x, wt, [2, 2], [2, 2], [0, 0], [1, 1], [0, 0], 1);
730 assert_eq!(g.shape(z), &Shape::new(&[1, 8, 16, 16], f));
731 }
732
733 #[test]
734 fn batch_norm_training_and_full_shapes() {
735 let mut g = Graph::new("bn");
736 let f = DType::F32;
737 let x = g.input("x", Shape::new(&[2, 3, 4, 5], f));
738 let gamma = g.param("g", Shape::new(&[3], f));
739 let beta = g.param("b", Shape::new(&[3], f));
740 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);
743 assert_eq!(g.shape(ones), &Shape::new(&[3], f));
744 assert!(matches!(g.node(ones).op, Op::Constant { .. }));
745 }
746
747 #[test]
748 fn zeros_tensor_shape_and_no_params() {
749 let mut g = Graph::new("zeros");
750 let f = DType::F32;
751 let z = g.zeros(&[2, 3, 1, 4], f);
752 assert_eq!(g.shape(z), &Shape::new(&[2, 3, 1, 4], f));
753 assert!(matches!(g.node(z).op, Op::Constant { .. }));
755 }
756
757 #[test]
758 fn inferred_layer_norm2d() {
759 let mut g = Graph::new("ln2d");
760 let f = DType::F32;
761 let x = g.input("x", Shape::new(&[1, 4, 8, 8], f));
762 let gamma = g.param("g", Shape::new(&[4], f));
763 let beta = g.param("b", Shape::new(&[4], f));
764 let y = g.layer_norm2d(x, gamma, beta, 1e-6);
765 assert_eq!(g.shape(y), &Shape::new(&[1, 4, 8, 8], f));
766 }
767
768 #[test]
769 fn inferred_matmul_bias_gelu() {
770 let mut g = Graph::new("test");
771 let x = g.input("x", Shape::new(&[4, 15, 384], DType::F32));
772 let w = g.param("w", Shape::new(&[384, 1536], DType::F32));
773 let b = g.param("b", Shape::new(&[1536], DType::F32));
774
775 let mm = g.mm(x, w);
777 let add = g.add(mm, b);
778 let out = g.gelu(add);
779 g.set_outputs(vec![out]);
780
781 assert_eq!(g.shape(mm), &Shape::new(&[4, 15, 1536], DType::F32));
782 assert_eq!(g.shape(add), &Shape::new(&[4, 15, 1536], DType::F32));
783 assert_eq!(g.shape(out), &Shape::new(&[4, 15, 1536], DType::F32));
784 }
785
786 #[test]
787 fn inferred_bert_ffn() {
788 let mut g = Graph::new("bert_ffn");
789 let f = DType::F32;
790 let h = 384;
791 let int = 1536;
792
793 let x = g.input("x", Shape::new(&[4, 15, h], f));
794 let int_w = g.param("int.w", Shape::new(&[h, int], f));
795 let int_b = g.param("int.b", Shape::new(&[int], f));
796 let out_w = g.param("out.w", Shape::new(&[int, h], f));
797 let out_b = g.param("out.b", Shape::new(&[h], f));
798 let gamma = g.param("g", Shape::new(&[h], f));
799 let beta = g.param("b", Shape::new(&[h], f));
800
801 let mm1 = g.mm(x, int_w);
802 let a1 = g.add(mm1, int_b);
803 let ffn = g.gelu(a1);
804 let mm2 = g.mm(ffn, out_w);
805 let out = g.add(mm2, out_b);
806 let res = g.add(out, x);
807 let normed = g.ln(res, gamma, beta, 1e-12);
808 g.set_outputs(vec![normed]);
809
810 assert_eq!(g.shape(ffn), &Shape::new(&[4, 15, int], f));
811 assert_eq!(g.shape(out), &Shape::new(&[4, 15, h], f));
812 assert_eq!(g.shape(normed), &Shape::new(&[4, 15, h], f));
813 }
814
815 #[test]
816 fn inferred_gather_reshape() {
817 let mut g = Graph::new("test");
818 let table = g.param("emb", Shape::new(&[30522, 384], DType::F32));
819 let ids = g.input("ids", Shape::new(&[4, 15], DType::I64));
820
821 let gathered = g.gather_(table, ids, 0);
822 assert_eq!(g.shape(gathered), &Shape::new(&[4, 15, 384], DType::F32));
823
824 let reshaped = g.reshape_(gathered, vec![60, 384]);
825 assert_eq!(g.shape(reshaped), &Shape::new(&[60, 384], DType::F32));
826
827 let transposed = g.transpose_(reshaped, vec![1, 0]);
828 assert_eq!(g.shape(transposed), &Shape::new(&[384, 60], DType::F32));
829 }
830
831 #[test]
832 fn inferred_constant_broadcasts() {
833 let mut g = Graph::new("const");
834 let x = g.input("x", Shape::new(&[2, 3], DType::F32));
835 let c = g.constant(2.0, DType::F32);
836 assert_eq!(g.shape(c), &Shape::scalar(DType::F32));
837 let y = g.mul(x, c);
838 assert_eq!(g.shape(y), &Shape::new(&[2, 3], DType::F32));
839 }
840
841 #[test]
842 fn inferred_constant_f16_via_cast() {
843 let mut g = Graph::new("const_f16");
844 let c = g.constant(1.5, DType::F16);
845 assert_eq!(g.shape(c), &Shape::scalar(DType::F16));
846 let x = g.input("x", Shape::new(&[2], DType::F16));
847 let y = g.add(x, c);
848 assert_eq!(g.shape(y), &Shape::new(&[2], DType::F16));
849 }
850
851 #[test]
852 fn inferred_constant_arithmetic_chain() {
853 let mut g = Graph::new("const_chain");
854 let x = g.input("x", Shape::new(&[4], DType::F32));
855 let one = g.constant(1.0, DType::F32);
856 let two = g.constant(2.0, DType::F32);
857 let sum = g.add(x, one);
858 let y = g.div(sum, two);
859 assert_eq!(g.shape(y), &Shape::new(&[4], DType::F32));
860 g.set_outputs(vec![y]);
861 }
862
863 #[test]
864 fn try_constant_rejects_out_of_range() {
865 let mut g = Graph::new("try_const");
866 let err = g.try_constant(128.0, DType::I8).unwrap_err();
867 assert!(err.contains("out of range"));
868 }
869
870 #[test]
871 fn try_constant_f16_via_cast() {
872 let mut g = Graph::new("try_const_f16");
873 let c = g.try_constant(1.5, DType::F16).unwrap();
874 assert_eq!(g.shape(c), &Shape::scalar(DType::F16));
875 }
876
877 #[test]
878 fn inferred_reduce_softmax() {
879 let mut g = Graph::new("test");
880 let x = g.input("x", Shape::new(&[4, 15, 384], DType::F32));
881
882 let s = g.sm(x, -1);
883 assert_eq!(g.shape(s), &Shape::new(&[4, 15, 384], DType::F32));
884
885 let m = g.mean(x, vec![2], false);
886 assert_eq!(g.shape(m), &Shape::new(&[4, 15], DType::F32));
887
888 let mk = g.mean(x, vec![2], true);
889 assert_eq!(g.shape(mk), &Shape::new(&[4, 15, 1], DType::F32));
890 }
891}