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 conv2d(
81 &mut self,
82 input: NodeId,
83 weight: NodeId,
84 kernel_size: [usize; 2],
85 stride: [usize; 2],
86 padding: [usize; 2],
87 dilation: [usize; 2],
88 groups: usize,
89 ) -> NodeId;
90 fn conv_transpose2d(
91 &mut self,
92 input: NodeId,
93 weight: NodeId,
94 kernel_size: [usize; 2],
95 stride: [usize; 2],
96 padding: [usize; 2],
97 dilation: [usize; 2],
98 output_padding: [usize; 2],
99 groups: usize,
100 ) -> NodeId;
101
102 fn sum(&mut self, x: NodeId, axes: Vec<usize>, keep_dim: bool) -> NodeId;
104 fn mean(&mut self, x: NodeId, axes: Vec<usize>, keep_dim: bool) -> NodeId;
105 fn sm(&mut self, x: NodeId, axis: i32) -> NodeId;
106
107 fn reshape_(&mut self, x: NodeId, new_shape: Vec<i64>) -> NodeId;
109 fn transpose_(&mut self, x: NodeId, perm: Vec<usize>) -> NodeId;
110 fn narrow_(&mut self, x: NodeId, axis: usize, start: usize, len: usize) -> NodeId;
111 fn concat_(&mut self, inputs: Vec<NodeId>, axis: usize) -> NodeId;
112 fn gather_(&mut self, table: NodeId, indices: NodeId, axis: usize) -> NodeId;
113
114 fn eq(&mut self, lhs: NodeId, rhs: NodeId) -> NodeId;
116 fn lt(&mut self, lhs: NodeId, rhs: NodeId) -> NodeId;
117
118 fn attention_(
120 &mut self,
121 q: NodeId,
122 k: NodeId,
123 v: NodeId,
124 mask: NodeId,
125 num_heads: usize,
126 head_dim: usize,
127 ) -> NodeId;
128
129 fn rope(&mut self, x: NodeId, cos: NodeId, sin: NodeId, head_dim: usize) -> NodeId;
131 fn rope_n(
133 &mut self,
134 x: NodeId,
135 cos: NodeId,
136 sin: NodeId,
137 head_dim: usize,
138 n_rot: usize,
139 ) -> NodeId;
140 fn rope_styled(
142 &mut self,
143 x: NodeId,
144 cos: NodeId,
145 sin: NodeId,
146 head_dim: usize,
147 style: crate::op::RopeStyle,
148 ) -> NodeId;
149 fn rope_n_styled(
151 &mut self,
152 x: NodeId,
153 cos: NodeId,
154 sin: NodeId,
155 head_dim: usize,
156 n_rot: usize,
157 style: crate::op::RopeStyle,
158 ) -> NodeId;
159
160 fn cast(&mut self, x: NodeId, to: DType) -> NodeId;
162
163 fn constant(&mut self, value: f64, dtype: DType) -> NodeId;
167
168 fn try_constant(&mut self, value: f64, dtype: DType) -> Result<NodeId, String>;
173
174 fn stop_gradient(&mut self, x: NodeId) -> NodeId;
180}
181
182impl GraphExt for Graph {
183 fn mm(&mut self, lhs: NodeId, rhs: NodeId) -> NodeId {
184 let s =
185 shape::matmul_shape(self.shape(lhs), self.shape(rhs)).expect("matmul shape inference");
186 self.matmul(lhs, rhs, s)
187 }
188
189 fn add(&mut self, lhs: NodeId, rhs: NodeId) -> NodeId {
190 let s = shape::binary_shape(self.shape(lhs), self.shape(rhs)).expect("add shape inference");
191 self.binary(BinaryOp::Add, lhs, rhs, s)
192 }
193
194 fn sub(&mut self, lhs: NodeId, rhs: NodeId) -> NodeId {
195 let s = shape::binary_shape(self.shape(lhs), self.shape(rhs)).expect("sub shape inference");
196 self.binary(BinaryOp::Sub, lhs, rhs, s)
197 }
198
199 fn mul(&mut self, lhs: NodeId, rhs: NodeId) -> NodeId {
200 let s = shape::binary_shape(self.shape(lhs), self.shape(rhs)).expect("mul shape inference");
201 self.binary(BinaryOp::Mul, lhs, rhs, s)
202 }
203
204 fn div(&mut self, lhs: NodeId, rhs: NodeId) -> NodeId {
205 let s = shape::binary_shape(self.shape(lhs), self.shape(rhs)).expect("div shape inference");
206 self.binary(BinaryOp::Div, lhs, rhs, s)
207 }
208
209 fn gelu(&mut self, x: NodeId) -> NodeId {
210 let s = shape::unary_shape(self.shape(x));
211 self.activation(Activation::Gelu, x, s)
212 }
213
214 fn gelu_approx(&mut self, x: NodeId) -> NodeId {
215 let s = shape::unary_shape(self.shape(x));
216 self.activation(Activation::GeluApprox, x, s)
217 }
218
219 fn silu(&mut self, x: NodeId) -> NodeId {
220 let s = shape::unary_shape(self.shape(x));
221 self.activation(Activation::Silu, x, s)
222 }
223
224 fn relu(&mut self, x: NodeId) -> NodeId {
225 let s = shape::unary_shape(self.shape(x));
226 self.activation(Activation::Relu, x, s)
227 }
228
229 fn exp(&mut self, x: NodeId) -> NodeId {
230 let s = shape::unary_shape(self.shape(x));
231 self.activation(Activation::Exp, x, s)
232 }
233
234 fn sqrt(&mut self, x: NodeId) -> NodeId {
235 let s = shape::unary_shape(self.shape(x));
236 self.activation(Activation::Sqrt, x, s)
237 }
238
239 fn neg(&mut self, x: NodeId) -> NodeId {
240 let s = shape::unary_shape(self.shape(x));
241 self.activation(Activation::Neg, x, s)
242 }
243
244 fn tanh(&mut self, x: NodeId) -> NodeId {
245 let s = shape::unary_shape(self.shape(x));
246 self.activation(Activation::Tanh, x, s)
247 }
248
249 fn ln(&mut self, x: NodeId, gamma: NodeId, beta: NodeId, eps: f32) -> NodeId {
250 let s = shape::unary_shape(self.shape(x));
251 self.layer_norm(x, gamma, beta, -1, eps, s)
252 }
253
254 fn layer_norm2d(&mut self, x: NodeId, gamma: NodeId, beta: NodeId, eps: f32) -> NodeId {
255 Graph::layer_norm2d(self, x, gamma, beta, eps)
256 }
257
258 fn group_norm(
259 &mut self,
260 x: NodeId,
261 gamma: NodeId,
262 beta: NodeId,
263 num_groups: usize,
264 eps: f32,
265 ) -> NodeId {
266 Graph::group_norm(self, x, gamma, beta, num_groups, eps)
267 }
268
269 fn conv2d(
270 &mut self,
271 input: NodeId,
272 weight: NodeId,
273 kernel_size: [usize; 2],
274 stride: [usize; 2],
275 padding: [usize; 2],
276 dilation: [usize; 2],
277 groups: usize,
278 ) -> NodeId {
279 Graph::conv2d(
280 self,
281 input,
282 weight,
283 kernel_size,
284 stride,
285 padding,
286 dilation,
287 groups,
288 )
289 }
290
291 fn conv_transpose2d(
292 &mut self,
293 input: NodeId,
294 weight: NodeId,
295 kernel_size: [usize; 2],
296 stride: [usize; 2],
297 padding: [usize; 2],
298 dilation: [usize; 2],
299 output_padding: [usize; 2],
300 groups: usize,
301 ) -> NodeId {
302 Graph::conv_transpose2d(
303 self,
304 input,
305 weight,
306 kernel_size,
307 stride,
308 padding,
309 dilation,
310 output_padding,
311 groups,
312 )
313 }
314
315 fn rms_norm(&mut self, x: NodeId, gamma: NodeId, beta: NodeId, eps: f32) -> NodeId {
316 let s = shape::unary_shape(self.shape(x));
317 self.add_node(Op::RmsNorm { axis: -1, eps }, vec![x, gamma, beta], s)
318 }
319
320 fn sum(&mut self, x: NodeId, axes: Vec<usize>, keep_dim: bool) -> NodeId {
321 let s =
322 shape::reduce_shape(self.shape(x), &axes, keep_dim).expect("reduce shape inference");
323 self.reduce(x, ReduceOp::Sum, axes, keep_dim, s)
324 }
325
326 fn mean(&mut self, x: NodeId, axes: Vec<usize>, keep_dim: bool) -> NodeId {
327 let s =
328 shape::reduce_shape(self.shape(x), &axes, keep_dim).expect("reduce shape inference");
329 self.reduce(x, ReduceOp::Mean, axes, keep_dim, s)
330 }
331
332 fn sm(&mut self, x: NodeId, axis: i32) -> NodeId {
333 let s = shape::softmax_shape(self.shape(x));
334 self.softmax(x, axis, s)
335 }
336
337 fn reshape_(&mut self, x: NodeId, new_shape: Vec<i64>) -> NodeId {
338 let s = shape::reshape_shape(self.shape(x), &new_shape).expect("reshape shape inference");
339 self.reshape(x, new_shape, s)
340 }
341
342 fn transpose_(&mut self, x: NodeId, perm: Vec<usize>) -> NodeId {
343 let s = shape::transpose_shape(self.shape(x), &perm).expect("transpose shape inference");
344 self.add_node(Op::Transpose { perm }, vec![x], s)
345 }
346
347 fn narrow_(&mut self, x: NodeId, axis: usize, start: usize, len: usize) -> NodeId {
348 let s = shape::narrow_shape(self.shape(x), axis, len).expect("narrow shape inference");
349 self.add_node(Op::Narrow { axis, start, len }, vec![x], s)
350 }
351
352 fn concat_(&mut self, inputs: Vec<NodeId>, axis: usize) -> NodeId {
353 let shapes: Vec<&Shape> = inputs.iter().map(|&id| self.shape(id)).collect();
354 let s = shape::concat_shape(&shapes, axis).expect("concat shape inference");
355 self.concat(inputs, axis, s)
356 }
357
358 fn gather_(&mut self, table: NodeId, indices: NodeId, axis: usize) -> NodeId {
359 let s = shape::gather_shape(self.shape(table), self.shape(indices), axis)
360 .expect("gather shape inference");
361 self.gather(table, indices, axis, s)
362 }
363
364 fn eq(&mut self, lhs: NodeId, rhs: NodeId) -> NodeId {
365 let s = shape::compare_shape(self.shape(lhs), self.shape(rhs))
366 .expect("compare shape inference");
367 self.add_node(Op::Compare(CmpOp::Eq), vec![lhs, rhs], s)
368 }
369
370 fn lt(&mut self, lhs: NodeId, rhs: NodeId) -> NodeId {
371 let s = shape::compare_shape(self.shape(lhs), self.shape(rhs))
372 .expect("compare shape inference");
373 self.add_node(Op::Compare(CmpOp::Lt), vec![lhs, rhs], s)
374 }
375
376 fn attention_(
377 &mut self,
378 q: NodeId,
379 k: NodeId,
380 v: NodeId,
381 mask: NodeId,
382 num_heads: usize,
383 head_dim: usize,
384 ) -> NodeId {
385 let s = shape::attention_shape(self.shape(q));
386 self.attention(q, k, v, mask, num_heads, head_dim, s)
387 }
388
389 fn rope(&mut self, x: NodeId, cos: NodeId, sin: NodeId, head_dim: usize) -> NodeId {
390 self.rope_n(x, cos, sin, head_dim, head_dim)
391 }
392
393 fn rope_n(
394 &mut self,
395 x: NodeId,
396 cos: NodeId,
397 sin: NodeId,
398 head_dim: usize,
399 n_rot: usize,
400 ) -> NodeId {
401 self.rope_n_styled(x, cos, sin, head_dim, n_rot, crate::op::RopeStyle::NeoX)
402 }
403
404 fn rope_styled(
405 &mut self,
406 x: NodeId,
407 cos: NodeId,
408 sin: NodeId,
409 head_dim: usize,
410 style: crate::op::RopeStyle,
411 ) -> NodeId {
412 self.rope_n_styled(x, cos, sin, head_dim, head_dim, style)
413 }
414
415 fn rope_n_styled(
416 &mut self,
417 x: NodeId,
418 cos: NodeId,
419 sin: NodeId,
420 head_dim: usize,
421 n_rot: usize,
422 style: crate::op::RopeStyle,
423 ) -> NodeId {
424 assert!(
425 n_rot <= head_dim && n_rot.is_multiple_of(2),
426 "rope_n: n_rot={n_rot} must be even and <= head_dim={head_dim}"
427 );
428 let s = shape::unary_shape(self.shape(x));
429 self.add_node(
430 Op::Rope {
431 head_dim,
432 n_rot,
433 style,
434 },
435 vec![x, cos, sin],
436 s,
437 )
438 }
439
440 fn cast(&mut self, x: NodeId, to: DType) -> NodeId {
441 let s = shape::cast_shape(self.shape(x), to);
442 self.add_node(Op::Cast { to }, vec![x], s)
443 }
444
445 fn try_constant(&mut self, value: f64, dtype: DType) -> Result<NodeId, String> {
446 if matches!(dtype, DType::F16 | DType::BF16) {
447 let f32_id = self.try_constant(value, DType::F32)?;
448 return Ok(self.cast(f32_id, dtype));
449 }
450 let data = scalar_constant_bytes(value, dtype)?;
451 Ok(self.add_node(Op::Constant { data }, vec![], Shape::scalar(dtype)))
452 }
453
454 fn constant(&mut self, value: f64, dtype: DType) -> NodeId {
455 self.try_constant(value, dtype)
456 .expect("scalar constant encoding")
457 }
458
459 fn stop_gradient(&mut self, x: NodeId) -> NodeId {
460 let s = shape::unary_shape(self.shape(x));
461 self.add_node(Op::StopGradient, vec![x], s)
462 }
463}
464
465#[cfg(test)]
466mod tests {
467 use super::*;
468
469 #[test]
470 fn inferred_conv2d_and_conv_transpose2d() {
471 let mut g = Graph::new("conv");
472 let f = DType::F32;
473 let x = g.input("x", Shape::new(&[1, 4, 8, 8], f));
474 let w = g.param("w", Shape::new(&[8, 2, 3, 3], f));
475 let y = g.conv2d(x, w, [3, 3], [1, 1], [1, 1], [1, 1], 2);
476 assert_eq!(g.shape(y), &Shape::new(&[1, 8, 8, 8], f));
477
478 let wt = g.param("wt", Shape::new(&[4, 8, 2, 2], f));
479 let z = g.conv_transpose2d(x, wt, [2, 2], [2, 2], [0, 0], [1, 1], [0, 0], 1);
480 assert_eq!(g.shape(z), &Shape::new(&[1, 8, 16, 16], f));
481 }
482
483 #[test]
484 fn inferred_layer_norm2d() {
485 let mut g = Graph::new("ln2d");
486 let f = DType::F32;
487 let x = g.input("x", Shape::new(&[1, 4, 8, 8], f));
488 let gamma = g.param("g", Shape::new(&[4], f));
489 let beta = g.param("b", Shape::new(&[4], f));
490 let y = g.layer_norm2d(x, gamma, beta, 1e-6);
491 assert_eq!(g.shape(y), &Shape::new(&[1, 4, 8, 8], f));
492 }
493
494 #[test]
495 fn inferred_matmul_bias_gelu() {
496 let mut g = Graph::new("test");
497 let x = g.input("x", Shape::new(&[4, 15, 384], DType::F32));
498 let w = g.param("w", Shape::new(&[384, 1536], DType::F32));
499 let b = g.param("b", Shape::new(&[1536], DType::F32));
500
501 let mm = g.mm(x, w);
503 let add = g.add(mm, b);
504 let out = g.gelu(add);
505 g.set_outputs(vec![out]);
506
507 assert_eq!(g.shape(mm), &Shape::new(&[4, 15, 1536], DType::F32));
508 assert_eq!(g.shape(add), &Shape::new(&[4, 15, 1536], DType::F32));
509 assert_eq!(g.shape(out), &Shape::new(&[4, 15, 1536], DType::F32));
510 }
511
512 #[test]
513 fn inferred_bert_ffn() {
514 let mut g = Graph::new("bert_ffn");
515 let f = DType::F32;
516 let h = 384;
517 let int = 1536;
518
519 let x = g.input("x", Shape::new(&[4, 15, h], f));
520 let int_w = g.param("int.w", Shape::new(&[h, int], f));
521 let int_b = g.param("int.b", Shape::new(&[int], f));
522 let out_w = g.param("out.w", Shape::new(&[int, h], f));
523 let out_b = g.param("out.b", Shape::new(&[h], f));
524 let gamma = g.param("g", Shape::new(&[h], f));
525 let beta = g.param("b", Shape::new(&[h], f));
526
527 let mm1 = g.mm(x, int_w);
528 let a1 = g.add(mm1, int_b);
529 let ffn = g.gelu(a1);
530 let mm2 = g.mm(ffn, out_w);
531 let out = g.add(mm2, out_b);
532 let res = g.add(out, x);
533 let normed = g.ln(res, gamma, beta, 1e-12);
534 g.set_outputs(vec![normed]);
535
536 assert_eq!(g.shape(ffn), &Shape::new(&[4, 15, int], f));
537 assert_eq!(g.shape(out), &Shape::new(&[4, 15, h], f));
538 assert_eq!(g.shape(normed), &Shape::new(&[4, 15, h], f));
539 }
540
541 #[test]
542 fn inferred_gather_reshape() {
543 let mut g = Graph::new("test");
544 let table = g.param("emb", Shape::new(&[30522, 384], DType::F32));
545 let ids = g.input("ids", Shape::new(&[4, 15], DType::I64));
546
547 let gathered = g.gather_(table, ids, 0);
548 assert_eq!(g.shape(gathered), &Shape::new(&[4, 15, 384], DType::F32));
549
550 let reshaped = g.reshape_(gathered, vec![60, 384]);
551 assert_eq!(g.shape(reshaped), &Shape::new(&[60, 384], DType::F32));
552
553 let transposed = g.transpose_(reshaped, vec![1, 0]);
554 assert_eq!(g.shape(transposed), &Shape::new(&[384, 60], DType::F32));
555 }
556
557 #[test]
558 fn inferred_constant_broadcasts() {
559 let mut g = Graph::new("const");
560 let x = g.input("x", Shape::new(&[2, 3], DType::F32));
561 let c = g.constant(2.0, DType::F32);
562 assert_eq!(g.shape(c), &Shape::scalar(DType::F32));
563 let y = g.mul(x, c);
564 assert_eq!(g.shape(y), &Shape::new(&[2, 3], DType::F32));
565 }
566
567 #[test]
568 fn inferred_constant_f16_via_cast() {
569 let mut g = Graph::new("const_f16");
570 let c = g.constant(1.5, DType::F16);
571 assert_eq!(g.shape(c), &Shape::scalar(DType::F16));
572 let x = g.input("x", Shape::new(&[2], DType::F16));
573 let y = g.add(x, c);
574 assert_eq!(g.shape(y), &Shape::new(&[2], DType::F16));
575 }
576
577 #[test]
578 fn inferred_constant_arithmetic_chain() {
579 let mut g = Graph::new("const_chain");
580 let x = g.input("x", Shape::new(&[4], DType::F32));
581 let one = g.constant(1.0, DType::F32);
582 let two = g.constant(2.0, DType::F32);
583 let sum = g.add(x, one);
584 let y = g.div(sum, two);
585 assert_eq!(g.shape(y), &Shape::new(&[4], DType::F32));
586 g.set_outputs(vec![y]);
587 }
588
589 #[test]
590 fn try_constant_rejects_out_of_range() {
591 let mut g = Graph::new("try_const");
592 let err = g.try_constant(128.0, DType::I8).unwrap_err();
593 assert!(err.contains("out of range"));
594 }
595
596 #[test]
597 fn try_constant_f16_via_cast() {
598 let mut g = Graph::new("try_const_f16");
599 let c = g.try_constant(1.5, DType::F16).unwrap();
600 assert_eq!(g.shape(c), &Shape::scalar(DType::F16));
601 }
602
603 #[test]
604 fn inferred_reduce_softmax() {
605 let mut g = Graph::new("test");
606 let x = g.input("x", Shape::new(&[4, 15, 384], DType::F32));
607
608 let s = g.sm(x, -1);
609 assert_eq!(g.shape(s), &Shape::new(&[4, 15, 384], DType::F32));
610
611 let m = g.mean(x, vec![2], false);
612 assert_eq!(g.shape(m), &Shape::new(&[4, 15], DType::F32));
613
614 let mk = g.mean(x, vec![2], true);
615 assert_eq!(g.shape(mk), &Shape::new(&[4, 15, 1], DType::F32));
616 }
617}