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