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}