1use crate::infer::GraphExt;
13use crate::op::Activation;
14use crate::{Graph, NodeId, Op, Shape};
15
16impl Graph {
17 pub fn linear_bias(&mut self, input: NodeId, weight: NodeId, bias: Option<NodeId>) -> NodeId {
19 let mm = self.mm(input, weight);
20 match bias {
21 Some(b) => self.add(mm, b),
22 None => mm,
23 }
24 }
25
26 pub fn linear_bias_act(
28 &mut self,
29 input: NodeId,
30 weight: NodeId,
31 bias: Option<NodeId>,
32 activation: Option<Activation>,
33 ) -> NodeId {
34 let x = self.linear_bias(input, weight, bias);
35 activation.map_or(x, |act| self.activation_by_kind(x, act))
36 }
37
38 pub fn linear_fused(
41 &mut self,
42 input: NodeId,
43 weight: NodeId,
44 bias: NodeId,
45 activation: Option<Activation>,
46 out_shape: Shape,
47 ) -> NodeId {
48 self.fused_matmul_bias_act(input, weight, bias, activation, out_shape)
49 }
50
51 pub fn shared_matmul_pair(
58 &mut self,
59 input: NodeId,
60 w_first: NodeId,
61 w_second: NodeId,
62 ) -> (NodeId, NodeId) {
63 let first = self.mm(input, w_first);
64 let second = self.mm(input, w_second);
65 (first, second)
66 }
67
68 pub fn swiglu_ffn(
73 &mut self,
74 input: NodeId,
75 up_w: NodeId,
76 gate_w: NodeId,
77 down_w: NodeId,
78 ) -> NodeId {
79 let (up, gate) = self.shared_matmul_pair(input, up_w, gate_w);
80 let gate_silu = self.silu(gate);
81 let hidden = self.mul(up, gate_silu);
82 self.mm(hidden, down_w)
83 }
84
85 pub fn fused_swiglu_ffn(
90 &mut self,
91 input: NodeId,
92 up_w: NodeId,
93 gate_w: NodeId,
94 down_w: NodeId,
95 out_shape: Shape,
96 ) -> NodeId {
97 let wu_shape = self.shape(up_w);
98 let wg_shape = self.shape(gate_w);
99 let k = wu_shape.dim(0).unwrap_static();
100 let n_up = wu_shape.dim(1).unwrap_static();
101 let n_gate = wg_shape.dim(1).unwrap_static();
102 debug_assert_eq!(wu_shape.dim(0), wg_shape.dim(0));
103
104 let concat_shape = Shape::new(&[k, n_up + n_gate], wu_shape.dtype());
105 let concat_w = self.concat(vec![up_w, gate_w], 1, concat_shape);
106
107 let input_shape = self.shape(input);
108 let out_rank = input_shape.rank();
109 let dtype = input_shape.dtype();
110 let mut cat_dims: Vec<usize> = (0..out_rank)
111 .map(|i| input_shape.dim(i).unwrap_static())
112 .collect();
113 cat_dims[out_rank - 1] = n_up + n_gate;
114 let cat_shape = Shape::new(&cat_dims, dtype);
115 let cat_mm = self.matmul(input, concat_w, cat_shape);
116
117 let mut hidden_dims = cat_dims;
118 hidden_dims[out_rank - 1] = n_up;
119 let hidden_shape = Shape::new(&hidden_dims, dtype);
120 let hidden = self.add_node(
121 Op::FusedSwiGLU {
122 cast_to: None,
123 gate_first: false,
124 },
125 vec![cat_mm],
126 hidden_shape,
127 );
128
129 let _ = out_shape;
130 self.mm(hidden, down_w)
131 }
132
133 fn activation_by_kind(&mut self, x: NodeId, act: Activation) -> NodeId {
134 match act {
135 Activation::Gelu => self.gelu(x),
136 Activation::GeluApprox => self.gelu_approx(x),
137 Activation::Silu => self.silu(x),
138 Activation::Relu => self.relu(x),
139 Activation::Exp => self.exp(x),
140 Activation::Sqrt => self.sqrt(x),
141 Activation::Neg => self.neg(x),
142 Activation::Tanh => self.tanh(x),
143 Activation::Sigmoid => {
144 let s = self.shape(x).clone();
145 self.activation(Activation::Sigmoid, x, s)
146 }
147 Activation::Log => {
148 let s = self.shape(x).clone();
149 self.activation(Activation::Log, x, s)
150 }
151 _ => {
152 let s = self.shape(x).clone();
153 self.activation(act, x, s)
154 }
155 }
156 }
157}
158
159#[cfg(test)]
160mod tests {
161 use super::*;
162 use crate::op::BinaryOp;
163 use crate::{DType, Op};
164
165 fn f32_shape(dims: &[usize]) -> Shape {
166 Shape::new(dims, DType::F32)
167 }
168
169 #[test]
170 fn linear_bias_act_emits_canonical_chain() {
171 let mut g = Graph::new("linear");
172 let x = g.input("x", f32_shape(&[4, 8]));
173 let w = g.param("w", f32_shape(&[8, 16]));
174 let b = g.param("b", f32_shape(&[16]));
175 let out = g.linear_bias_act(x, w, Some(b), Some(Activation::Silu));
176 g.set_outputs(vec![out]);
177
178 let act = g.node(out);
179 assert!(matches!(act.op, Op::Activation(Activation::Silu)));
180 let add = g.node(act.inputs[0]);
181 assert!(matches!(add.op, Op::Binary(BinaryOp::Add)));
182 let mm = g.node(add.inputs[0]);
183 assert!(matches!(mm.op, Op::MatMul));
184 }
185}