1use std::ops::{Deref, DerefMut};
8
9use crate::hir::{FusionPolicy, HirModule, HirNodeId, LowerError};
10use crate::inspect::{inspect_hir, inspect_lir, inspect_mir};
11use crate::lir::LirModule;
12use crate::mir::MirModule;
13use crate::op::Activation;
14use crate::op::MaskKind;
15use crate::quant::QuantScheme;
16use crate::{Graph, NodeId, Op, Shape};
17
18#[derive(Debug, Clone, Copy, PartialEq, Eq)]
20pub enum GraphStage {
21 Hir,
22 Mir,
23 Lir,
24}
25
26#[derive(Debug, Clone)]
27enum Stage {
28 Hir(HirModule),
29 Mir(MirModule),
30 Lir(LirModule),
31}
32
33#[derive(Debug, Clone)]
35pub struct GraphModule {
36 stage: Stage,
37}
38
39impl GraphModule {
40 pub fn define(
41 name: impl Into<String>,
42 build: impl FnOnce(&mut HirModule) -> HirNodeId,
43 ) -> Self {
44 let mut hir = HirModule::new(name);
45 let out = build(&mut hir);
46 hir.set_outputs(vec![out]);
47 Self {
48 stage: Stage::Hir(hir),
49 }
50 }
51
52 pub fn hir(name: impl Into<String>) -> Self {
54 Self {
55 stage: Stage::Hir(HirModule::new(name)),
56 }
57 }
58
59 pub fn mir(name: impl Into<String>) -> Self {
61 Self {
62 stage: Stage::Mir(MirModule::new(name)),
63 }
64 }
65
66 pub fn from_hir(hir: HirModule) -> Self {
67 Self {
68 stage: Stage::Hir(hir),
69 }
70 }
71
72 pub fn from_graph(graph: Graph) -> Self {
73 Self {
74 stage: Stage::Mir(MirModule::from_graph(graph)),
75 }
76 }
77
78 pub fn from_mir(mir: MirModule) -> Self {
79 Self {
80 stage: Stage::Mir(mir),
81 }
82 }
83
84 pub fn from_lir(lir: LirModule) -> Self {
85 Self {
86 stage: Stage::Lir(lir),
87 }
88 }
89
90 pub fn block(
91 hir: &mut HirModule,
92 name: impl Into<String>,
93 build: impl FnOnce(&mut HirModule) -> HirNodeId,
94 ) -> HirNodeId {
95 hir.named(name, build)
96 }
97
98 pub fn fusion_policy(&self) -> Option<FusionPolicy> {
99 self.as_hir().map(|h| h.fusion_policy)
100 }
101
102 pub fn with_fusion_policy(mut self, policy: FusionPolicy) -> Self {
103 if let Stage::Hir(h) = &mut self.stage {
104 h.fusion_policy = policy;
105 } else {
106 panic!("GraphModule::with_fusion_policy requires HIR stage");
107 }
108 self
109 }
110
111 pub fn set_outputs(&mut self, outputs: Vec<HirNodeId>) {
116 match &mut self.stage {
117 Stage::Hir(h) => h.set_outputs(outputs),
118 Stage::Mir(m) => m.set_outputs(outputs.into_iter().map(|h| NodeId(h.0)).collect()),
119 Stage::Lir(l) => l
120 .mir
121 .set_outputs(outputs.into_iter().map(|h| NodeId(h.0)).collect()),
122 }
123 }
124
125 pub fn set_hir_outputs(&mut self, outputs: Vec<HirNodeId>) {
126 self.set_outputs(outputs);
127 }
128
129 pub fn finish_hir(mut self, output: HirNodeId) -> Self {
131 self.set_hir_outputs(vec![output]);
132 self
133 }
134
135 fn hir_mut(&mut self) -> &mut HirModule {
136 self.as_hir_mut()
137 .expect("GraphModule: HIR builder methods require HIR stage — use GraphModule::hir() or Graph::define()")
138 }
139
140 pub fn input(&mut self, name: impl Into<String>, shape: Shape) -> HirNodeId {
143 match &mut self.stage {
144 Stage::Hir(h) => h.input(name, shape),
145 Stage::Mir(m) => {
146 let id = m.as_graph_mut().input(name, shape);
147 HirNodeId(id.0)
148 }
149 Stage::Lir(l) => {
150 let id = l.mir.as_graph_mut().input(name, shape);
151 HirNodeId(id.0)
152 }
153 }
154 }
155
156 pub fn param(&mut self, name: impl Into<String>, shape: Shape) -> HirNodeId {
157 match &mut self.stage {
158 Stage::Hir(h) => h.param(name, shape),
159 Stage::Mir(m) => {
160 let id = m.as_graph_mut().param(name, shape);
161 HirNodeId(id.0)
162 }
163 Stage::Lir(l) => {
164 let id = l.mir.as_graph_mut().param(name, shape);
165 HirNodeId(id.0)
166 }
167 }
168 }
169
170 pub fn linear(
171 &mut self,
172 x: HirNodeId,
173 weight: HirNodeId,
174 bias: Option<HirNodeId>,
175 activation: Option<Activation>,
176 out_shape: Shape,
177 ) -> HirNodeId {
178 self.hir_mut()
179 .linear(x, weight, bias, activation, out_shape)
180 }
181
182 pub fn linear_fused(
183 &mut self,
184 x: HirNodeId,
185 weight: HirNodeId,
186 bias: HirNodeId,
187 activation: Option<Activation>,
188 out_shape: Shape,
189 ) -> HirNodeId {
190 self.hir_mut()
191 .linear_fused(x, weight, bias, activation, out_shape)
192 }
193
194 pub fn shared_linear_pair(
195 &mut self,
196 x: HirNodeId,
197 w_first: HirNodeId,
198 w_second: HirNodeId,
199 out_shape: Shape,
200 ) -> (HirNodeId, HirNodeId) {
201 self.hir_mut()
202 .shared_linear_pair(x, w_first, w_second, out_shape)
203 }
204
205 pub fn swiglu_ffn(
206 &mut self,
207 x: HirNodeId,
208 up_w: HirNodeId,
209 gate_w: HirNodeId,
210 down_w: HirNodeId,
211 out_shape: Shape,
212 ) -> HirNodeId {
213 self.hir_mut()
214 .swiglu_ffn(x, up_w, gate_w, down_w, out_shape)
215 }
216
217 pub fn residual_rms_norm(
218 &mut self,
219 x: HirNodeId,
220 residual: HirNodeId,
221 gamma: HirNodeId,
222 beta: HirNodeId,
223 eps: f32,
224 out_shape: Shape,
225 ) -> HirNodeId {
226 self.hir_mut()
227 .residual_rms_norm(x, residual, gamma, beta, eps, out_shape)
228 }
229
230 pub fn attention(
231 &mut self,
232 q: HirNodeId,
233 k: HirNodeId,
234 v: HirNodeId,
235 mask: Option<HirNodeId>,
236 num_heads: usize,
237 head_dim: usize,
238 mask_kind: MaskKind,
239 out_shape: Shape,
240 ) -> HirNodeId {
241 self.hir_mut()
242 .attention(q, k, v, mask, num_heads, head_dim, mask_kind, out_shape)
243 }
244
245 pub fn depthwise_conv1d_causal(
246 &mut self,
247 input: HirNodeId,
248 weight: HirNodeId,
249 left_pad: HirNodeId,
250 kernel_size: usize,
251 out_shape: Shape,
252 ) -> HirNodeId {
253 self.hir_mut()
254 .depthwise_conv1d_causal(input, weight, left_pad, kernel_size, out_shape)
255 }
256
257 pub fn dequant_matmul(
258 &mut self,
259 x: HirNodeId,
260 w: HirNodeId,
261 scale: Option<HirNodeId>,
262 zp: Option<HirNodeId>,
263 scheme: QuantScheme,
264 out_shape: Shape,
265 ) -> HirNodeId {
266 self.hir_mut()
267 .dequant_matmul(x, w, scale, zp, scheme, out_shape)
268 }
269
270 pub fn gated_delta_net(
271 &mut self,
272 q: HirNodeId,
273 k: HirNodeId,
274 v: HirNodeId,
275 g: HirNodeId,
276 beta: HirNodeId,
277 state_size: usize,
278 out_shape: Shape,
279 ) -> HirNodeId {
280 self.hir_mut()
281 .gated_delta_net(q, k, v, g, beta, state_size, out_shape)
282 }
283
284 pub fn gated_delta_net_carry(
285 &mut self,
286 q: HirNodeId,
287 k: HirNodeId,
288 v: HirNodeId,
289 g: HirNodeId,
290 beta: HirNodeId,
291 state: HirNodeId,
292 state_size: usize,
293 out_shape: Shape,
294 ) -> HirNodeId {
295 self.hir_mut()
296 .gated_delta_net_carry(q, k, v, g, beta, state, state_size, out_shape)
297 }
298
299 pub fn rope(
300 &mut self,
301 x: HirNodeId,
302 cos: HirNodeId,
303 sin: HirNodeId,
304 head_dim: usize,
305 n_rot: usize,
306 out_shape: Shape,
307 ) -> HirNodeId {
308 self.hir_mut().rope(x, cos, sin, head_dim, n_rot, out_shape)
309 }
310
311 pub fn rms_norm(
312 &mut self,
313 x: HirNodeId,
314 gamma: HirNodeId,
315 beta: HirNodeId,
316 eps: f32,
317 out_shape: Shape,
318 ) -> HirNodeId {
319 self.hir_mut().rms_norm(x, gamma, beta, eps, out_shape)
320 }
321
322 pub fn hir_mir(&mut self, op: Op, inputs: Vec<HirNodeId>, shape: Shape) -> HirNodeId {
323 self.hir_mut().mir(op, inputs, shape)
324 }
325
326 pub fn named(
327 &mut self,
328 name: impl Into<String>,
329 build: impl FnOnce(&mut HirModule) -> HirNodeId,
330 ) -> HirNodeId {
331 self.hir_mut().named(name, build)
332 }
333
334 pub fn stage(&self) -> GraphStage {
335 match &self.stage {
336 Stage::Hir(_) => GraphStage::Hir,
337 Stage::Mir(_) => GraphStage::Mir,
338 Stage::Lir(_) => GraphStage::Lir,
339 }
340 }
341
342 pub fn name(&self) -> &str {
343 match &self.stage {
344 Stage::Hir(h) => &h.name,
345 Stage::Mir(m) => m.name(),
346 Stage::Lir(l) => l.name(),
347 }
348 }
349
350 pub fn lower(self) -> Result<Self, LowerError> {
351 match self.stage {
352 Stage::Hir(hir) => Ok(Self {
353 stage: Stage::Mir(hir.lower_to_mir()?),
354 }),
355 other => Ok(Self { stage: other }),
356 }
357 }
358
359 pub fn into_hir(self) -> Option<HirModule> {
360 match self.stage {
361 Stage::Hir(h) => Some(h),
362 _ => None,
363 }
364 }
365
366 pub fn into_mir(self) -> Result<MirModule, LowerError> {
367 match self.stage {
368 Stage::Hir(hir) => hir.lower_to_mir(),
369 Stage::Mir(m) => Ok(m),
370 Stage::Lir(l) => Ok(l.mir),
371 }
372 }
373
374 pub fn into_lir(self) -> Option<LirModule> {
375 match self.stage {
376 Stage::Lir(l) => Some(l),
377 _ => None,
378 }
379 }
380
381 pub fn into_graph(self) -> Result<Graph, LowerError> {
382 Ok(self.into_mir()?.into_graph())
383 }
384
385 pub fn as_hir(&self) -> Option<&HirModule> {
386 match &self.stage {
387 Stage::Hir(h) => Some(h),
388 _ => None,
389 }
390 }
391
392 pub fn as_hir_mut(&mut self) -> Option<&mut HirModule> {
393 match &mut self.stage {
394 Stage::Hir(h) => Some(h),
395 _ => None,
396 }
397 }
398
399 pub fn as_mir(&self) -> Option<&MirModule> {
400 match &self.stage {
401 Stage::Mir(m) => Some(m),
402 Stage::Lir(l) => Some(&l.mir),
403 _ => None,
404 }
405 }
406
407 pub fn as_lir(&self) -> Option<&LirModule> {
408 match &self.stage {
409 Stage::Lir(l) => Some(l),
410 _ => None,
411 }
412 }
413
414 pub fn as_graph(&self) -> Option<&Graph> {
415 match &self.stage {
416 Stage::Mir(m) => Some(m.as_graph()),
417 Stage::Lir(l) => Some(l.as_graph()),
418 Stage::Hir(_) => None,
419 }
420 }
421
422 pub fn inspect(&self) -> String {
423 match &self.stage {
424 Stage::Hir(h) => inspect_hir(h),
425 Stage::Mir(m) => inspect_mir(m),
426 Stage::Lir(l) => inspect_lir(l),
427 }
428 }
429}
430
431impl Deref for GraphModule {
432 type Target = Graph;
433
434 fn deref(&self) -> &Graph {
435 self.as_graph()
436 .expect("GraphModule: HIR stage — call lower() before accessing MIR Graph")
437 }
438}
439
440impl DerefMut for GraphModule {
441 fn deref_mut(&mut self) -> &mut Graph {
442 match &mut self.stage {
443 Stage::Mir(m) => m.as_graph_mut(),
444 Stage::Lir(l) => l.mir.as_graph_mut(),
445 Stage::Hir(_) => panic!("GraphModule: HIR stage — use as_hir_mut() or lower() first"),
446 }
447 }
448}
449
450impl From<Graph> for GraphModule {
451 fn from(graph: Graph) -> Self {
452 Self::from_graph(graph)
453 }
454}
455
456impl TryFrom<GraphModule> for Graph {
457 type Error = LowerError;
458
459 fn try_from(module: GraphModule) -> Result<Self, LowerError> {
460 module.into_graph()
461 }
462}
463
464impl From<MirModule> for GraphModule {
465 fn from(mir: MirModule) -> Self {
466 Self::from_mir(mir)
467 }
468}
469
470impl From<HirModule> for GraphModule {
471 fn from(hir: HirModule) -> Self {
472 Self::from_hir(hir)
473 }
474}
475
476impl std::fmt::Display for GraphModule {
477 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
478 match &self.stage {
479 Stage::Hir(h) => write!(f, "{h}"),
480 Stage::Mir(m) => write!(f, "{m}"),
481 Stage::Lir(l) => write!(f, "lir @{}", l.name()),
482 }
483 }
484}
485
486#[cfg(test)]
487mod tests {
488 use super::*;
489 use crate::DType;
490 use crate::Graph;
491 use crate::Shape;
492
493 fn f32_shape(d: &[usize]) -> Shape {
494 Shape::new(d, DType::F32)
495 }
496
497 #[test]
498 fn define_lowers_to_mir_graph() {
499 let module = GraphModule::define("m", |m| {
500 let x = m.input("x", f32_shape(&[2, 8]));
501 let w = m.param("w", f32_shape(&[8, 8]));
502 m.linear(x, w, None, None, f32_shape(&[2, 8]))
503 });
504 assert_eq!(module.stage(), GraphStage::Hir);
505 let module = module.lower().expect("lower");
506 assert_eq!(module.stage(), GraphStage::Mir);
507 assert!(module.len() >= 3);
508 }
509
510 #[test]
511 fn mir_module_deref_builds_graph() {
512 let mut module = GraphModule::mir("raw");
513 let x = module.input("x", f32_shape(&[4]));
514 module.set_outputs(vec![x]);
515 assert_eq!(module.len(), 1);
516 }
517
518 #[test]
519 fn hir_module_block_builders_via_graph_module() {
520 use crate::quant::QuantScheme;
521
522 let mut module = GraphModule::hir("layer");
523 let x = module.input("x", f32_shape(&[2, 128]));
524 let w = module.param("w", f32_shape(&[128, 128]));
525 let y = module.dequant_matmul(x, w, None, None, QuantScheme::GgufQ4K, f32_shape(&[2, 128]));
526 module.set_outputs(vec![y]);
527 assert_eq!(module.stage(), GraphStage::Hir);
528
529 let module = module.lower().expect("lower");
530 assert_eq!(module.stage(), GraphStage::Mir);
531 assert!(module.len() >= 3);
532 }
533
534 #[test]
535 fn graph_hir_entry_matches_define() {
536 let via_graph = Graph::hir("m");
537 let via_define = Graph::define("m", |m| {
538 let x = m.input("x", f32_shape(&[4]));
539 m.rms_norm(x, x, x, 1e-5, f32_shape(&[4]))
540 });
541 assert_eq!(via_graph.stage(), GraphStage::Hir);
542 assert_eq!(via_define.stage(), GraphStage::Hir);
543 }
544}