Skip to main content

tenferro_tensor/
config.rs

1use crate::{Error, Result, ValidationError};
2
3const DOT_GENERAL_OP: &str = "dot_general";
4
5fn invalid_dot_general_config(message: impl Into<String>) -> Error {
6    Error::invalid_argument(DOT_GENERAL_OP, "dot_general_config", message)
7}
8
9/// DotGeneral dimension configuration.
10///
11/// Records only the dim-numbering roles (contracting / batch; free is derived).
12/// Rank info travels with the enclosing `StdTensorOp::DotGeneral` variant at
13/// the trace/StdTensorOp layer, and with `ExecInstruction::output_shapes` at
14/// the exec layer. This separation makes it structurally impossible for
15/// stored ranks to drift from actual tensor ranks (issue #664).
16///
17/// The output shape is `[lhs_free..., rhs_free..., batch...]` (col-major
18/// batch-trailing convention). Batch dims have the largest stride so that
19/// each batch slice occupies a contiguous block of memory.
20///
21/// # Examples
22///
23/// ```rust
24/// use tenferro_tensor::DotGeneralConfig;
25///
26/// let config = DotGeneralConfig {
27///     lhs_contracting_dims: vec![1],
28///     rhs_contracting_dims: vec![0],
29///     lhs_batch_dims: vec![],
30///     rhs_batch_dims: vec![],
31/// };
32/// ```
33#[derive(Clone, Debug, Hash, PartialEq, Eq)]
34pub struct DotGeneralConfig {
35    pub lhs_contracting_dims: Vec<usize>,
36    pub rhs_contracting_dims: Vec<usize>,
37    pub lhs_batch_dims: Vec<usize>,
38    pub rhs_batch_dims: Vec<usize>,
39}
40
41impl DotGeneralConfig {
42    fn check_no_duplicates(dims: &[usize], label: &'static str) -> Result<()> {
43        let mut seen = std::collections::HashSet::new();
44        for &d in dims {
45            if !seen.insert(d) {
46                return Err(Error::validation(
47                    DOT_GENERAL_OP,
48                    ValidationError::DuplicateAxis {
49                        axis: d,
50                        role: label,
51                    },
52                ));
53            }
54        }
55        Ok(())
56    }
57
58    /// Validate that all dimension indices are within range for the given
59    /// explicit ranks and that no axis appears in multiple roles.
60    ///
61    /// Call sites supply the actual operand ranks (from the tensor shapes they
62    /// have in hand). The config itself carries only the dim-numbering roles.
63    ///
64    /// # Examples
65    ///
66    /// ```rust
67    /// use tenferro_tensor::DotGeneralConfig;
68    ///
69    /// let config = DotGeneralConfig {
70    ///     lhs_contracting_dims: vec![1],
71    ///     rhs_contracting_dims: vec![0],
72    ///     lhs_batch_dims: vec![],
73    ///     rhs_batch_dims: vec![],
74    /// };
75    /// config.validate_dims_with_ranks(2, 2).unwrap();
76    /// ```
77    /// # Errors
78    ///
79    /// Returns [`crate::Error::Validation`] with an axis, duplicate-axis,
80    /// or configuration source when the dimension roles are invalid.
81    pub fn validate_dims_with_ranks(&self, lhs_rank: usize, rhs_rank: usize) -> Result<()> {
82        for &d in &self.lhs_contracting_dims {
83            if d >= lhs_rank {
84                return Err(Error::validation(
85                    DOT_GENERAL_OP,
86                    ValidationError::AxisOutOfBounds {
87                        axis: d,
88                        rank: lhs_rank,
89                    },
90                ));
91            }
92        }
93        for &d in &self.rhs_contracting_dims {
94            if d >= rhs_rank {
95                return Err(Error::validation(
96                    DOT_GENERAL_OP,
97                    ValidationError::AxisOutOfBounds {
98                        axis: d,
99                        rank: rhs_rank,
100                    },
101                ));
102            }
103        }
104        for &d in &self.lhs_batch_dims {
105            if d >= lhs_rank {
106                return Err(Error::validation(
107                    DOT_GENERAL_OP,
108                    ValidationError::AxisOutOfBounds {
109                        axis: d,
110                        rank: lhs_rank,
111                    },
112                ));
113            }
114        }
115        for &d in &self.rhs_batch_dims {
116            if d >= rhs_rank {
117                return Err(Error::validation(
118                    DOT_GENERAL_OP,
119                    ValidationError::AxisOutOfBounds {
120                        axis: d,
121                        rank: rhs_rank,
122                    },
123                ));
124            }
125        }
126        Self::check_no_duplicates(&self.lhs_contracting_dims, "lhs_contracting_dims")?;
127        Self::check_no_duplicates(&self.rhs_contracting_dims, "rhs_contracting_dims")?;
128        Self::check_no_duplicates(&self.lhs_batch_dims, "lhs_batch_dims")?;
129        Self::check_no_duplicates(&self.rhs_batch_dims, "rhs_batch_dims")?;
130        for &d in &self.lhs_contracting_dims {
131            if self.lhs_batch_dims.contains(&d) {
132                return Err(Error::validation(
133                    DOT_GENERAL_OP,
134                    ValidationError::AxisRoleConflict {
135                        axis: d,
136                        first_role: "lhs contracting",
137                        second_role: "lhs batch",
138                    },
139                ));
140            }
141        }
142        for &d in &self.rhs_contracting_dims {
143            if self.rhs_batch_dims.contains(&d) {
144                return Err(Error::validation(
145                    DOT_GENERAL_OP,
146                    ValidationError::AxisRoleConflict {
147                        axis: d,
148                        first_role: "rhs contracting",
149                        second_role: "rhs batch",
150                    },
151                ));
152            }
153        }
154        if self.lhs_contracting_dims.len() != self.rhs_contracting_dims.len() {
155            return Err(invalid_dot_general_config(format!(
156                "lhs/rhs contracting dim counts differ ({} vs {})",
157                self.lhs_contracting_dims.len(),
158                self.rhs_contracting_dims.len()
159            )));
160        }
161        if self.lhs_batch_dims.len() != self.rhs_batch_dims.len() {
162            return Err(invalid_dot_general_config(format!(
163                "lhs/rhs batch dim counts differ ({} vs {})",
164                self.lhs_batch_dims.len(),
165                self.rhs_batch_dims.len()
166            )));
167        }
168        Ok(())
169    }
170}
171
172/// Comparison direction.
173///
174/// # Examples
175///
176/// ```rust
177/// use tenferro_tensor::CompareDir;
178///
179/// let dir = CompareDir::Eq;
180/// ```
181#[derive(Clone, Debug, Hash, PartialEq, Eq)]
182pub enum CompareDir {
183    Eq,
184    Lt,
185    Le,
186    Gt,
187    Ge,
188}
189
190/// StableHLO gather dimension configuration.
191///
192/// # Examples
193///
194/// ```rust
195/// use tenferro_tensor::GatherConfig;
196///
197/// let config = GatherConfig {
198///     offset_dims: vec![],
199///     collapsed_slice_dims: vec![0],
200///     start_index_map: vec![0],
201///     index_vector_dim: 1,
202///     slice_sizes: vec![1],
203/// };
204/// ```
205#[derive(Clone, Debug, Hash, PartialEq, Eq)]
206pub struct GatherConfig {
207    pub offset_dims: Vec<usize>,
208    pub collapsed_slice_dims: Vec<usize>,
209    pub start_index_map: Vec<usize>,
210    pub index_vector_dim: usize,
211    pub slice_sizes: Vec<usize>,
212}
213
214/// StableHLO scatter dimension configuration.
215///
216/// # Examples
217///
218/// ```rust
219/// use tenferro_tensor::ScatterConfig;
220///
221/// let config = ScatterConfig {
222///     update_window_dims: vec![],
223///     inserted_window_dims: vec![0],
224///     scatter_dims_to_operand_dims: vec![0],
225///     index_vector_dim: 1,
226/// };
227/// ```
228#[derive(Clone, Debug, Hash, PartialEq, Eq)]
229pub struct ScatterConfig {
230    pub update_window_dims: Vec<usize>,
231    pub inserted_window_dims: Vec<usize>,
232    pub scatter_dims_to_operand_dims: Vec<usize>,
233    pub index_vector_dim: usize,
234}
235
236/// Slice configuration.
237///
238/// # Examples
239///
240/// ```rust
241/// use tenferro_tensor::SliceConfig;
242///
243/// let config = SliceConfig {
244///     starts: vec![0],
245///     limits: vec![2],
246///     strides: vec![1],
247/// };
248/// ```
249#[derive(Clone, Debug, Hash, PartialEq, Eq)]
250pub struct SliceConfig {
251    pub starts: Vec<usize>,
252    pub limits: Vec<usize>,
253    pub strides: Vec<usize>,
254}
255
256/// StableHLO pad configuration.
257///
258/// # Examples
259///
260/// ```rust
261/// use tenferro_tensor::PadConfig;
262///
263/// let config = PadConfig {
264///     edge_padding_low: vec![1, 1],
265///     edge_padding_high: vec![1, 1],
266///     interior_padding: vec![0, 0],
267/// };
268/// ```
269#[derive(Clone, Debug, Hash, PartialEq, Eq)]
270pub struct PadConfig {
271    pub edge_padding_low: Vec<i64>,
272    pub edge_padding_high: Vec<i64>,
273    pub interior_padding: Vec<i64>,
274}