tenferro_tensor/config.rs
1use crate::{Error, Result};
2
3const DOT_GENERAL_OP: &str = "dot_general";
4
5fn invalid_dot_general_config(message: impl Into<String>) -> Error {
6 Error::InvalidConfig {
7 op: DOT_GENERAL_OP,
8 message: message.into(),
9 }
10}
11
12/// DotGeneral dimension configuration.
13///
14/// Records only the dim-numbering roles (contracting / batch; free is derived).
15/// Rank info travels with the enclosing `StdTensorOp::DotGeneral` variant at
16/// the trace/StdTensorOp layer, and with `ExecInstruction::output_shapes` at
17/// the exec layer. This separation makes it structurally impossible for
18/// stored ranks to drift from actual tensor ranks (issue #664).
19///
20/// The output shape is `[lhs_free..., rhs_free..., batch...]` (col-major
21/// batch-trailing convention). Batch dims have the largest stride so that
22/// each batch slice occupies a contiguous block of memory.
23///
24/// # Examples
25///
26/// ```rust
27/// use tenferro_tensor::DotGeneralConfig;
28///
29/// let config = DotGeneralConfig {
30/// lhs_contracting_dims: vec![1],
31/// rhs_contracting_dims: vec![0],
32/// lhs_batch_dims: vec![],
33/// rhs_batch_dims: vec![],
34/// };
35/// ```
36#[derive(Clone, Debug, Hash, PartialEq, Eq)]
37pub struct DotGeneralConfig {
38 pub lhs_contracting_dims: Vec<usize>,
39 pub rhs_contracting_dims: Vec<usize>,
40 pub lhs_batch_dims: Vec<usize>,
41 pub rhs_batch_dims: Vec<usize>,
42}
43
44impl DotGeneralConfig {
45 fn check_no_duplicates(dims: &[usize], label: &str) -> Result<()> {
46 let mut seen = std::collections::HashSet::new();
47 for &d in dims {
48 if !seen.insert(d) {
49 return Err(invalid_dot_general_config(format!(
50 "{} contains duplicate dim {}",
51 label, d
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 pub fn validate_dims_with_ranks(&self, lhs_rank: usize, rhs_rank: usize) -> Result<()> {
78 for &d in &self.lhs_contracting_dims {
79 if d >= lhs_rank {
80 return Err(invalid_dot_general_config(format!(
81 "lhs_contracting_dim {} out of bounds for lhs_rank {}",
82 d, lhs_rank
83 )));
84 }
85 }
86 for &d in &self.rhs_contracting_dims {
87 if d >= rhs_rank {
88 return Err(invalid_dot_general_config(format!(
89 "rhs_contracting_dim {} out of bounds for rhs_rank {}",
90 d, rhs_rank
91 )));
92 }
93 }
94 for &d in &self.lhs_batch_dims {
95 if d >= lhs_rank {
96 return Err(invalid_dot_general_config(format!(
97 "lhs_batch_dim {} out of bounds for lhs_rank {}",
98 d, lhs_rank
99 )));
100 }
101 }
102 for &d in &self.rhs_batch_dims {
103 if d >= rhs_rank {
104 return Err(invalid_dot_general_config(format!(
105 "rhs_batch_dim {} out of bounds for rhs_rank {}",
106 d, rhs_rank
107 )));
108 }
109 }
110 Self::check_no_duplicates(&self.lhs_contracting_dims, "lhs_contracting_dims")?;
111 Self::check_no_duplicates(&self.rhs_contracting_dims, "rhs_contracting_dims")?;
112 Self::check_no_duplicates(&self.lhs_batch_dims, "lhs_batch_dims")?;
113 Self::check_no_duplicates(&self.rhs_batch_dims, "rhs_batch_dims")?;
114 for &d in &self.lhs_contracting_dims {
115 if self.lhs_batch_dims.contains(&d) {
116 return Err(invalid_dot_general_config(format!(
117 "lhs dim {} appears in both contracting and batch dims",
118 d
119 )));
120 }
121 }
122 for &d in &self.rhs_contracting_dims {
123 if self.rhs_batch_dims.contains(&d) {
124 return Err(invalid_dot_general_config(format!(
125 "rhs dim {} appears in both contracting and batch dims",
126 d
127 )));
128 }
129 }
130 if self.lhs_contracting_dims.len() != self.rhs_contracting_dims.len() {
131 return Err(invalid_dot_general_config(format!(
132 "lhs/rhs contracting dim counts differ ({} vs {})",
133 self.lhs_contracting_dims.len(),
134 self.rhs_contracting_dims.len()
135 )));
136 }
137 if self.lhs_batch_dims.len() != self.rhs_batch_dims.len() {
138 return Err(invalid_dot_general_config(format!(
139 "lhs/rhs batch dim counts differ ({} vs {})",
140 self.lhs_batch_dims.len(),
141 self.rhs_batch_dims.len()
142 )));
143 }
144 Ok(())
145 }
146}
147
148/// Comparison direction.
149///
150/// # Examples
151///
152/// ```rust
153/// use tenferro_tensor::CompareDir;
154///
155/// let dir = CompareDir::Eq;
156/// ```
157#[derive(Clone, Debug, Hash, PartialEq, Eq)]
158pub enum CompareDir {
159 Eq,
160 Lt,
161 Le,
162 Gt,
163 Ge,
164}
165
166/// StableHLO gather dimension configuration.
167///
168/// # Examples
169///
170/// ```rust
171/// use tenferro_tensor::GatherConfig;
172///
173/// let config = GatherConfig {
174/// offset_dims: vec![],
175/// collapsed_slice_dims: vec![0],
176/// start_index_map: vec![0],
177/// index_vector_dim: 1,
178/// slice_sizes: vec![1],
179/// };
180/// ```
181#[derive(Clone, Debug, Hash, PartialEq, Eq)]
182pub struct GatherConfig {
183 pub offset_dims: Vec<usize>,
184 pub collapsed_slice_dims: Vec<usize>,
185 pub start_index_map: Vec<usize>,
186 pub index_vector_dim: usize,
187 pub slice_sizes: Vec<usize>,
188}
189
190/// StableHLO scatter dimension configuration.
191///
192/// # Examples
193///
194/// ```rust
195/// use tenferro_tensor::ScatterConfig;
196///
197/// let config = ScatterConfig {
198/// update_window_dims: vec![],
199/// inserted_window_dims: vec![0],
200/// scatter_dims_to_operand_dims: vec![0],
201/// index_vector_dim: 1,
202/// };
203/// ```
204#[derive(Clone, Debug, Hash, PartialEq, Eq)]
205pub struct ScatterConfig {
206 pub update_window_dims: Vec<usize>,
207 pub inserted_window_dims: Vec<usize>,
208 pub scatter_dims_to_operand_dims: Vec<usize>,
209 pub index_vector_dim: usize,
210}
211
212/// Slice configuration.
213///
214/// # Examples
215///
216/// ```rust
217/// use tenferro_tensor::SliceConfig;
218///
219/// let config = SliceConfig {
220/// starts: vec![0],
221/// limits: vec![2],
222/// strides: vec![1],
223/// };
224/// ```
225#[derive(Clone, Debug, Hash, PartialEq, Eq)]
226pub struct SliceConfig {
227 pub starts: Vec<usize>,
228 pub limits: Vec<usize>,
229 pub strides: Vec<usize>,
230}
231
232/// StableHLO pad configuration.
233///
234/// # Examples
235///
236/// ```rust
237/// use tenferro_tensor::PadConfig;
238///
239/// let config = PadConfig {
240/// edge_padding_low: vec![1, 1],
241/// edge_padding_high: vec![1, 1],
242/// interior_padding: vec![0, 0],
243/// };
244/// ```
245#[derive(Clone, Debug, Hash, PartialEq, Eq)]
246pub struct PadConfig {
247 pub edge_padding_low: Vec<i64>,
248 pub edge_padding_high: Vec<i64>,
249 pub interior_padding: Vec<i64>,
250}