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