Skip to main content

tenferro_runtime/graph/
lowering_view.rs

1use std::fmt;
2
3use tenferro_ops::ext_op::ExtensionOp;
4use tenferro_ops::ShapeExtent;
5use tenferro_tensor::{DType, DotGeneralConfig};
6
7use crate::exec::{ExecInstruction, ExecOp, ExecProgram};
8
9/// Read-only lowering view over a compiled graph program.
10///
11/// This view is for peer executor crates that need to translate a
12/// [`GraphProgram`](super::GraphProgram) without mutating the runtime-owned
13/// execution program.
14///
15/// # Examples
16///
17/// ```
18/// use tenferro_runtime::{GraphCompiler, TracedTensor};
19///
20/// let x = TracedTensor::from_vec_col_major(vec![1], vec![2.0_f64]).unwrap();
21/// let mut compiler = GraphCompiler::new();
22/// let y = x.neg().unwrap();
23/// let program = compiler.compile(&y).unwrap();
24/// let view = program.lowering_view();
25/// assert_eq!(view.output_slots().len(), 1);
26/// ```
27#[derive(Clone, Copy)]
28pub struct GraphProgramLoweringView<'a> {
29    exec: &'a ExecProgram,
30}
31
32impl fmt::Debug for GraphProgramLoweringView<'_> {
33    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
34        f.debug_struct("GraphProgramLoweringView")
35            .field("slot_count", &self.slot_count())
36            .field("input_count", &self.input_slots().len())
37            .field("output_count", &self.output_slots().len())
38            .field("instruction_count", &self.exec.instructions.len())
39            .finish()
40    }
41}
42
43impl<'a> GraphProgramLoweringView<'a> {
44    pub(crate) fn new(exec: &'a ExecProgram) -> Self {
45        Self { exec }
46    }
47
48    /// Return the number of execution slots used by the program.
49    ///
50    /// # Examples
51    ///
52    /// ```
53    /// use tenferro_runtime::{GraphCompiler, TracedTensor};
54    ///
55    /// let x = TracedTensor::from_vec_col_major(vec![1], vec![2.0_f64]).unwrap();
56    /// let mut compiler = GraphCompiler::new();
57    /// let y = x.neg().unwrap();
58    /// let program = compiler.compile(&y).unwrap();
59    /// assert!(program.lowering_view().slot_count() >= 1);
60    /// ```
61    pub fn slot_count(&self) -> usize {
62        self.exec.n_slots
63    }
64
65    /// Return the execution slots populated by graph inputs.
66    ///
67    /// # Examples
68    ///
69    /// ```
70    /// use tenferro_runtime::{GraphCompiler, TracedTensor};
71    ///
72    /// let x = TracedTensor::from_vec_col_major(vec![1], vec![2.0_f64]).unwrap();
73    /// let mut compiler = GraphCompiler::new();
74    /// let y = x.neg().unwrap();
75    /// let program = compiler.compile(&y).unwrap();
76    /// assert_eq!(program.lowering_view().input_slots().len(), 1);
77    /// ```
78    pub fn input_slots(&self) -> &'a [usize] {
79        &self.exec.input_slots
80    }
81
82    /// Return the execution slots used as program outputs.
83    ///
84    /// # Examples
85    ///
86    /// ```
87    /// use tenferro_runtime::{GraphCompiler, TracedTensor};
88    ///
89    /// let x = TracedTensor::from_vec_col_major(vec![1], vec![2.0_f64]).unwrap();
90    /// let mut compiler = GraphCompiler::new();
91    /// let y = x.neg().unwrap();
92    /// let program = compiler.compile(&y).unwrap();
93    /// assert_eq!(program.lowering_view().output_slots().len(), 1);
94    /// ```
95    pub fn output_slots(&self) -> &'a [usize] {
96        &self.exec.output_slots
97    }
98
99    /// Iterate over read-only instruction views in execution order.
100    ///
101    /// # Examples
102    ///
103    /// ```
104    /// use tenferro_runtime::{GraphCompiler, TracedTensor};
105    ///
106    /// let x = TracedTensor::from_vec_col_major(vec![1], vec![2.0_f64]).unwrap();
107    /// let mut compiler = GraphCompiler::new();
108    /// let y = x.neg().unwrap();
109    /// let program = compiler.compile(&y).unwrap();
110    /// assert!(program.lowering_view().instructions().count() >= 1);
111    /// ```
112    pub fn instructions(&self) -> impl ExactSizeIterator<Item = GraphInstructionView<'a>> + '_ {
113        self.exec.instructions.iter().map(GraphInstructionView::new)
114    }
115}
116
117/// Read-only lowering view over one execution instruction.
118///
119/// # Examples
120///
121/// ```
122/// use tenferro_runtime::{GraphCompiler, TracedTensor};
123///
124/// let x = TracedTensor::from_vec_col_major(vec![1], vec![2.0_f64]).unwrap();
125/// let mut compiler = GraphCompiler::new();
126/// let y = x.neg().unwrap();
127/// let program = compiler.compile(&y).unwrap();
128/// let inst = program.lowering_view().instructions().next().unwrap();
129/// assert_eq!(inst.output_slots().len(), 1);
130/// ```
131#[derive(Clone, Copy)]
132pub struct GraphInstructionView<'a> {
133    inst: &'a ExecInstruction,
134}
135
136impl fmt::Debug for GraphInstructionView<'_> {
137    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
138        f.debug_struct("GraphInstructionView")
139            .field("op", &self.op_name())
140            .field("input_count", &self.input_slots().len())
141            .field("output_count", &self.output_slots().len())
142            .field("dtype", &self.dtype())
143            .finish()
144    }
145}
146
147impl<'a> GraphInstructionView<'a> {
148    fn new(inst: &'a ExecInstruction) -> Self {
149        Self { inst }
150    }
151
152    /// Return the operation view for this instruction.
153    ///
154    /// # Examples
155    ///
156    /// ```
157    /// use tenferro_runtime::{GraphCompiler, GraphOpView, TracedTensor};
158    ///
159    /// let x = TracedTensor::from_vec_col_major(vec![1], vec![2.0_f64]).unwrap();
160    /// let mut compiler = GraphCompiler::new();
161    /// let y = x.neg().unwrap();
162    /// let program = compiler.compile(&y).unwrap();
163    /// let inst = program.lowering_view().instructions().next().unwrap();
164    /// assert!(matches!(inst.op(), GraphOpView::Negate));
165    /// ```
166    pub fn op(&self) -> GraphOpView<'a> {
167        match &self.inst.op {
168            ExecOp::Constant { dtype, bytes } => GraphOpView::Constant {
169                dtype: *dtype,
170                bytes,
171            },
172            ExecOp::Add => GraphOpView::Add,
173            ExecOp::Multiply => GraphOpView::Multiply,
174            ExecOp::Negate => GraphOpView::Negate,
175            ExecOp::Divide => GraphOpView::Divide,
176            ExecOp::Abs => GraphOpView::Abs,
177            ExecOp::Exp => GraphOpView::Exp,
178            ExecOp::Log => GraphOpView::Log,
179            ExecOp::Sin => GraphOpView::Sin,
180            ExecOp::Cos => GraphOpView::Cos,
181            ExecOp::Tanh => GraphOpView::Tanh,
182            ExecOp::Sqrt => GraphOpView::Sqrt,
183            ExecOp::Rsqrt => GraphOpView::Rsqrt,
184            ExecOp::Pow => GraphOpView::Pow,
185            ExecOp::Expm1 => GraphOpView::Expm1,
186            ExecOp::Log1p => GraphOpView::Log1p,
187            ExecOp::Convert { to } => GraphOpView::Convert { to: *to },
188            ExecOp::Reshape { .. } => GraphOpView::Reshape,
189            ExecOp::BroadcastInDim { dims, .. } => GraphOpView::BroadcastInDim { dims },
190            ExecOp::Transpose { perm } => GraphOpView::Transpose { perm },
191            ExecOp::ReduceSum { axes } => GraphOpView::ReduceSum { axes },
192            ExecOp::DotGeneral(config) => GraphOpView::DotGeneral { config },
193            ExecOp::Extension(op) => GraphOpView::Extension { op: op.as_ref() },
194            other => GraphOpView::Unsupported {
195                name: exec_op_name(other),
196            },
197        }
198    }
199
200    /// Return a stable operation name for diagnostics.
201    ///
202    /// # Examples
203    ///
204    /// ```
205    /// use tenferro_runtime::{GraphCompiler, TracedTensor};
206    ///
207    /// let x = TracedTensor::from_vec_col_major(vec![1], vec![2.0_f64]).unwrap();
208    /// let mut compiler = GraphCompiler::new();
209    /// let y = x.neg().unwrap();
210    /// let program = compiler.compile(&y).unwrap();
211    /// let inst = program.lowering_view().instructions().next().unwrap();
212    /// assert_eq!(inst.op_name(), "Negate");
213    /// ```
214    pub fn op_name(&self) -> &'static str {
215        self.op().name()
216    }
217
218    /// Return the input slots consumed by this instruction.
219    ///
220    /// # Examples
221    ///
222    /// ```
223    /// use tenferro_runtime::{GraphCompiler, TracedTensor};
224    ///
225    /// let x = TracedTensor::from_vec_col_major(vec![1], vec![2.0_f64]).unwrap();
226    /// let y = (&x + &x).unwrap();
227    /// let mut compiler = GraphCompiler::new();
228    /// let program = compiler.compile(&y).unwrap();
229    /// let inst = program.lowering_view().instructions().next().unwrap();
230    /// assert_eq!(inst.input_slots().len(), 2);
231    /// ```
232    pub fn input_slots(&self) -> &'a [usize] {
233        &self.inst.input_slots
234    }
235
236    /// Return the output slots written by this instruction.
237    ///
238    /// # Examples
239    ///
240    /// ```
241    /// use tenferro_runtime::{GraphCompiler, TracedTensor};
242    ///
243    /// let x = TracedTensor::from_vec_col_major(vec![1], vec![2.0_f64]).unwrap();
244    /// let mut compiler = GraphCompiler::new();
245    /// let y = x.neg().unwrap();
246    /// let program = compiler.compile(&y).unwrap();
247    /// let inst = program.lowering_view().instructions().next().unwrap();
248    /// assert_eq!(inst.output_slots().len(), 1);
249    /// ```
250    pub fn output_slots(&self) -> &'a [usize] {
251        &self.inst.output_slots
252    }
253
254    /// Return the dtype of this instruction's output.
255    ///
256    /// # Examples
257    ///
258    /// ```
259    /// use tenferro_runtime::{DType, GraphCompiler, TracedTensor};
260    ///
261    /// let x = TracedTensor::from_vec_col_major(vec![1], vec![2.0_f64]).unwrap();
262    /// let mut compiler = GraphCompiler::new();
263    /// let y = x.neg().unwrap();
264    /// let program = compiler.compile(&y).unwrap();
265    /// let inst = program.lowering_view().instructions().next().unwrap();
266    /// assert_eq!(inst.dtype(), DType::F64);
267    /// ```
268    pub fn dtype(&self) -> DType {
269        self.inst.dtype
270    }
271
272    /// Resolve an exact static output shape for this instruction.
273    ///
274    /// `input_shapes` must be ordered the same way as [`Self::input_slots`].
275    ///
276    /// # Examples
277    ///
278    /// ```
279    /// use tenferro_runtime::{GraphCompiler, TracedTensor};
280    ///
281    /// let x = TracedTensor::from_vec_col_major(vec![1], vec![2.0_f64]).unwrap();
282    /// let mut compiler = GraphCompiler::new();
283    /// let y = x.neg().unwrap();
284    /// let program = compiler.compile(&y).unwrap();
285    /// let inst = program.lowering_view().instructions().next().unwrap();
286    /// assert_eq!(inst.static_output_shape(0, &[&[1]]).unwrap(), vec![1]);
287    /// ```
288    pub fn static_output_shape(
289        &self,
290        output_index: usize,
291        input_shapes: &[&[usize]],
292    ) -> std::result::Result<Vec<usize>, GraphProgramLoweringShapeError> {
293        let extents = self.inst.output_extents.get(output_index).ok_or(
294            GraphProgramLoweringShapeError::MissingOutput {
295                op: self.op_name(),
296                output_index,
297            },
298        )?;
299        let mut shape = Vec::with_capacity(extents.len());
300        for (axis, extent) in extents.iter().enumerate() {
301            match extent {
302                ShapeExtent::Exact(dim) => shape.push(dim.eval(input_shapes).map_err(|err| {
303                    GraphProgramLoweringShapeError::InvalidDimExpr {
304                        op: self.op_name(),
305                        output_index,
306                        axis,
307                        source: err,
308                    }
309                })?),
310                ShapeExtent::UpperBound(_) => {
311                    return Err(GraphProgramLoweringShapeError::NonStatic {
312                        op: self.op_name(),
313                        output_index,
314                        axis,
315                        kind: "an upper bound",
316                    });
317                }
318                ShapeExtent::Unknown => {
319                    return Err(GraphProgramLoweringShapeError::NonStatic {
320                        op: self.op_name(),
321                        output_index,
322                        axis,
323                        kind: "unknown",
324                    });
325                }
326            }
327        }
328        Ok(shape)
329    }
330}
331
332/// Read-only operation view for graph lowering integrations.
333///
334/// Unsupported operation families are represented as [`GraphOpView::Unsupported`]
335/// so peer executors can emit precise diagnostics without depending on the raw
336/// execution IR.
337///
338/// # Examples
339///
340/// ```
341/// use tenferro_runtime::{GraphCompiler, GraphOpView, TracedTensor};
342///
343/// let x = TracedTensor::from_vec_col_major(vec![1], vec![2.0_f64]).unwrap();
344/// let mut compiler = GraphCompiler::new();
345/// let y = x.neg().unwrap();
346/// let program = compiler.compile(&y).unwrap();
347/// let op = program.lowering_view().instructions().next().unwrap().op();
348/// assert!(matches!(op, GraphOpView::Negate));
349/// ```
350#[derive(Clone, Copy)]
351pub enum GraphOpView<'a> {
352    /// Scalar constant payload.
353    Constant { dtype: DType, bytes: &'a [u8] },
354    /// Elementwise addition.
355    Add,
356    /// Elementwise multiplication.
357    Multiply,
358    /// Elementwise negation.
359    Negate,
360    /// Elementwise division.
361    Divide,
362    /// Elementwise absolute value.
363    Abs,
364    /// Elementwise exponential.
365    Exp,
366    /// Elementwise natural logarithm.
367    Log,
368    /// Elementwise sine.
369    Sin,
370    /// Elementwise cosine.
371    Cos,
372    /// Elementwise hyperbolic tangent.
373    Tanh,
374    /// Elementwise square root.
375    Sqrt,
376    /// Elementwise reciprocal square root.
377    Rsqrt,
378    /// Elementwise power.
379    Pow,
380    /// Elementwise exponential minus one.
381    Expm1,
382    /// Elementwise natural logarithm of one plus input.
383    Log1p,
384    /// Dtype conversion.
385    Convert { to: DType },
386    /// Shape-only reshape.
387    Reshape,
388    /// Broadcast with output-to-input dimension mapping.
389    BroadcastInDim { dims: &'a [usize] },
390    /// Transpose with output dimension permutation.
391    Transpose { perm: &'a [usize] },
392    /// Sum reduction.
393    ReduceSum { axes: &'a [usize] },
394    /// General dot/contraction.
395    DotGeneral { config: &'a DotGeneralConfig },
396    /// Extension operation with an owner-provided optional standard-op lowering.
397    Extension { op: &'a dyn ExtensionOp },
398    /// Operation outside the stable public lowering view.
399    Unsupported { name: &'static str },
400}
401
402impl fmt::Debug for GraphOpView<'_> {
403    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
404        match self {
405            Self::Constant { dtype, bytes } => f
406                .debug_struct("Constant")
407                .field("dtype", dtype)
408                .field("byte_len", &bytes.len())
409                .finish(),
410            Self::Add => f.write_str("Add"),
411            Self::Multiply => f.write_str("Multiply"),
412            Self::Negate => f.write_str("Negate"),
413            Self::Divide => f.write_str("Divide"),
414            Self::Abs => f.write_str("Abs"),
415            Self::Exp => f.write_str("Exp"),
416            Self::Log => f.write_str("Log"),
417            Self::Sin => f.write_str("Sin"),
418            Self::Cos => f.write_str("Cos"),
419            Self::Tanh => f.write_str("Tanh"),
420            Self::Sqrt => f.write_str("Sqrt"),
421            Self::Rsqrt => f.write_str("Rsqrt"),
422            Self::Pow => f.write_str("Pow"),
423            Self::Expm1 => f.write_str("Expm1"),
424            Self::Log1p => f.write_str("Log1p"),
425            Self::Convert { to } => f.debug_struct("Convert").field("to", to).finish(),
426            Self::Reshape => f.write_str("Reshape"),
427            Self::BroadcastInDim { dims } => f
428                .debug_struct("BroadcastInDim")
429                .field("dims", dims)
430                .finish(),
431            Self::Transpose { perm } => f.debug_struct("Transpose").field("perm", perm).finish(),
432            Self::ReduceSum { axes } => f.debug_struct("ReduceSum").field("axes", axes).finish(),
433            Self::DotGeneral { config } => f
434                .debug_struct("DotGeneral")
435                .field("config", config)
436                .finish(),
437            Self::Extension { op } => f
438                .debug_struct("Extension")
439                .field("family_id", &op.family_id())
440                .finish(),
441            Self::Unsupported { name } => {
442                f.debug_struct("Unsupported").field("name", name).finish()
443            }
444        }
445    }
446}
447
448impl GraphOpView<'_> {
449    /// Return the stable operation name used in diagnostics.
450    ///
451    /// # Examples
452    ///
453    /// ```
454    /// use tenferro_runtime::{GraphCompiler, TracedTensor};
455    ///
456    /// let x = TracedTensor::from_vec_col_major(vec![1], vec![2.0_f64]).unwrap();
457    /// let mut compiler = GraphCompiler::new();
458    /// let y = x.neg().unwrap();
459    /// let program = compiler.compile(&y).unwrap();
460    /// let op = program.lowering_view().instructions().next().unwrap().op();
461    /// assert_eq!(op.name(), "Negate");
462    /// ```
463    pub fn name(&self) -> &'static str {
464        match self {
465            Self::Constant { .. } => "Constant",
466            Self::Add => "Add",
467            Self::Multiply => "Multiply",
468            Self::Negate => "Negate",
469            Self::Divide => "Divide",
470            Self::Abs => "Abs",
471            Self::Exp => "Exp",
472            Self::Log => "Log",
473            Self::Sin => "Sin",
474            Self::Cos => "Cos",
475            Self::Tanh => "Tanh",
476            Self::Sqrt => "Sqrt",
477            Self::Rsqrt => "Rsqrt",
478            Self::Pow => "Pow",
479            Self::Expm1 => "Expm1",
480            Self::Log1p => "Log1p",
481            Self::Convert { .. } => "Convert",
482            Self::Reshape => "Reshape",
483            Self::BroadcastInDim { .. } => "BroadcastInDim",
484            Self::Transpose { .. } => "Transpose",
485            Self::ReduceSum { .. } => "ReduceSum",
486            Self::DotGeneral { .. } => "DotGeneral",
487            Self::Extension { .. } => "Extension",
488            Self::Unsupported { name } => name,
489        }
490    }
491}
492
493/// Error returned when a lowering view cannot resolve an exact output shape.
494///
495/// # Examples
496///
497/// ```
498/// use tenferro_runtime::GraphProgramLoweringShapeError;
499///
500/// let err = GraphProgramLoweringShapeError::MissingOutput {
501///     op: "Example",
502///     output_index: 0,
503/// };
504/// assert!(err.to_string().contains("Example"));
505/// ```
506#[derive(Clone, Debug, PartialEq, Eq, thiserror::Error)]
507pub enum GraphProgramLoweringShapeError {
508    /// The instruction has no metadata for the requested output.
509    #[error("ExecOp::{op} missing output_extents for output {output_index}")]
510    MissingOutput {
511        op: &'static str,
512        output_index: usize,
513    },
514    /// The instruction output has dynamic or unknown extent metadata.
515    #[error("ExecOp::{op} output {output_index} axis {axis} has non-static extent: {kind}")]
516    NonStatic {
517        op: &'static str,
518        output_index: usize,
519        axis: usize,
520        kind: &'static str,
521    },
522    /// Static shape evaluation failed for an exact dimension expression.
523    #[error(
524        "ExecOp::{op} output {output_index} axis {axis} has invalid dimension expression: {source}"
525    )]
526    InvalidDimExpr {
527        op: &'static str,
528        output_index: usize,
529        axis: usize,
530        source: tenferro_ops::dim_expr::DimExprEvalError,
531    },
532}
533
534fn exec_op_name(op: &ExecOp) -> &'static str {
535    match op {
536        ExecOp::Transpose { .. } => "Transpose",
537        ExecOp::Reshape { .. } => "Reshape",
538        ExecOp::BroadcastInDim { .. } => "BroadcastInDim",
539        ExecOp::Convert { .. } => "Convert",
540        ExecOp::Constant { .. } => "Constant",
541        ExecOp::DotGeneral(_) => "DotGeneral",
542        ExecOp::DotGeneralWithConj { .. } => "DotGeneralWithConj",
543        ExecOp::ReduceSum { .. } => "ReduceSum",
544        ExecOp::ExtractDiag { .. } => "ExtractDiag",
545        ExecOp::EmbedDiag { .. } => "EmbedDiag",
546        ExecOp::Tril { .. } => "Tril",
547        ExecOp::Triu { .. } => "Triu",
548        ExecOp::Add => "Add",
549        ExecOp::Subtract => "Subtract",
550        ExecOp::Multiply => "Multiply",
551        ExecOp::Negate => "Negate",
552        ExecOp::Conj => "Conj",
553        ExecOp::Divide => "Divide",
554        ExecOp::Remainder => "Remainder",
555        ExecOp::Abs => "Abs",
556        ExecOp::Sign => "Sign",
557        ExecOp::Maximum => "Maximum",
558        ExecOp::Minimum => "Minimum",
559        ExecOp::Compare(_) => "Compare",
560        ExecOp::Select => "Select",
561        ExecOp::Clamp => "Clamp",
562        ExecOp::Exp => "Exp",
563        ExecOp::Log => "Log",
564        ExecOp::Sin => "Sin",
565        ExecOp::Cos => "Cos",
566        ExecOp::Tanh => "Tanh",
567        ExecOp::Sqrt => "Sqrt",
568        ExecOp::Rsqrt => "Rsqrt",
569        ExecOp::Pow => "Pow",
570        ExecOp::Expm1 => "Expm1",
571        ExecOp::Log1p => "Log1p",
572        ExecOp::Gather(_) => "Gather",
573        ExecOp::GatherDynamicSliceSizes { .. } => "GatherDynamicSliceSizes",
574        ExecOp::Scatter(_) => "Scatter",
575        ExecOp::Slice(_) => "Slice",
576        ExecOp::DynamicSlice { .. } => "DynamicSlice",
577        ExecOp::DynamicUpdateSlice => "DynamicUpdateSlice",
578        ExecOp::Pad(_) => "Pad",
579        ExecOp::Concatenate { .. } => "Concatenate",
580        ExecOp::Reverse { .. } => "Reverse",
581        ExecOp::ShapeOf { .. } => "ShapeOf",
582        ExecOp::DynamicTruncate { .. } => "DynamicTruncate",
583        ExecOp::PadToMatch { .. } => "PadToMatch",
584        ExecOp::ReduceProd { .. } => "ReduceProd",
585        ExecOp::ReduceMax { .. } => "ReduceMax",
586        ExecOp::ReduceMin { .. } => "ReduceMin",
587        ExecOp::Extension(_) => "Extension",
588    }
589}
590
591#[cfg(test)]
592mod tests;