use tinyvec::TinyVec;
use crate::{Error, Result, ValidationError};
const DOT_GENERAL_OP: &str = "dot_general";
fn invalid_dot_general_config(message: impl Into<String>) -> Error {
Error::invalid_argument(DOT_GENERAL_OP, "dot_general_config", message)
}
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
pub struct DotGeneralConfig {
pub lhs_contracting_dims: TinyVec<[usize; 4]>,
pub rhs_contracting_dims: TinyVec<[usize; 4]>,
pub lhs_batch_dims: TinyVec<[usize; 4]>,
pub rhs_batch_dims: TinyVec<[usize; 4]>,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum DegenerateDotKind {
Hadamard,
OuterProduct,
ScalarTranspose,
FullContraction,
}
#[must_use]
pub fn degenerate_dot_kind<T: PartialEq>(
config: &DotGeneralConfig,
lhs_shape: &[T],
rhs_shape: &[T],
) -> Option<DegenerateDotKind> {
if !config.lhs_contracting_dims.is_empty() || !config.rhs_contracting_dims.is_empty() {
let all_contract = |rank: usize, dims: &[usize]| {
dims.len() == rank
&& dims
.iter()
.enumerate()
.all(|(position, &axis)| axis == position)
};
return (config.lhs_batch_dims.is_empty()
&& config.rhs_batch_dims.is_empty()
&& all_contract(lhs_shape.len(), &config.lhs_contracting_dims)
&& all_contract(rhs_shape.len(), &config.rhs_contracting_dims))
.then_some(DegenerateDotKind::FullContraction);
}
let identity_ordered = |dims: &[usize], rank: usize| {
dims.len() == rank
&& dims
.iter()
.enumerate()
.all(|(position, &axis)| axis == position)
};
if identity_ordered(&config.lhs_batch_dims, lhs_shape.len())
&& identity_ordered(&config.rhs_batch_dims, rhs_shape.len())
&& lhs_shape == rhs_shape
{
return Some(DegenerateDotKind::Hadamard);
}
if !config.lhs_batch_dims.is_empty() || !config.rhs_batch_dims.is_empty() {
return None;
}
match (lhs_shape.is_empty(), rhs_shape.is_empty()) {
(false, false) => Some(DegenerateDotKind::OuterProduct),
(true, false) | (false, true) => Some(DegenerateDotKind::ScalarTranspose),
(true, true) => None,
}
}
impl DotGeneralConfig {
fn check_no_duplicates(dims: &[usize], label: &'static str) -> Result<()> {
let mut seen = std::collections::HashSet::new();
for &d in dims {
if !seen.insert(d) {
return Err(Error::validation(
DOT_GENERAL_OP,
ValidationError::DuplicateAxis {
axis: d,
role: label,
},
));
}
}
Ok(())
}
pub fn validate_dims_with_ranks(&self, lhs_rank: usize, rhs_rank: usize) -> Result<()> {
for &d in &self.lhs_contracting_dims {
if d >= lhs_rank {
return Err(Error::validation(
DOT_GENERAL_OP,
ValidationError::AxisOutOfBounds {
axis: d,
rank: lhs_rank,
},
));
}
}
for &d in &self.rhs_contracting_dims {
if d >= rhs_rank {
return Err(Error::validation(
DOT_GENERAL_OP,
ValidationError::AxisOutOfBounds {
axis: d,
rank: rhs_rank,
},
));
}
}
for &d in &self.lhs_batch_dims {
if d >= lhs_rank {
return Err(Error::validation(
DOT_GENERAL_OP,
ValidationError::AxisOutOfBounds {
axis: d,
rank: lhs_rank,
},
));
}
}
for &d in &self.rhs_batch_dims {
if d >= rhs_rank {
return Err(Error::validation(
DOT_GENERAL_OP,
ValidationError::AxisOutOfBounds {
axis: d,
rank: rhs_rank,
},
));
}
}
Self::check_no_duplicates(&self.lhs_contracting_dims, "lhs_contracting_dims")?;
Self::check_no_duplicates(&self.rhs_contracting_dims, "rhs_contracting_dims")?;
Self::check_no_duplicates(&self.lhs_batch_dims, "lhs_batch_dims")?;
Self::check_no_duplicates(&self.rhs_batch_dims, "rhs_batch_dims")?;
for &d in &self.lhs_contracting_dims {
if self.lhs_batch_dims.contains(&d) {
return Err(Error::validation(
DOT_GENERAL_OP,
ValidationError::AxisRoleConflict {
axis: d,
first_role: "lhs contracting",
second_role: "lhs batch",
},
));
}
}
for &d in &self.rhs_contracting_dims {
if self.rhs_batch_dims.contains(&d) {
return Err(Error::validation(
DOT_GENERAL_OP,
ValidationError::AxisRoleConflict {
axis: d,
first_role: "rhs contracting",
second_role: "rhs batch",
},
));
}
}
if self.lhs_contracting_dims.len() != self.rhs_contracting_dims.len() {
return Err(invalid_dot_general_config(format!(
"lhs/rhs contracting dim counts differ ({} vs {})",
self.lhs_contracting_dims.len(),
self.rhs_contracting_dims.len()
)));
}
if self.lhs_batch_dims.len() != self.rhs_batch_dims.len() {
return Err(invalid_dot_general_config(format!(
"lhs/rhs batch dim counts differ ({} vs {})",
self.lhs_batch_dims.len(),
self.rhs_batch_dims.len()
)));
}
Ok(())
}
}
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
pub enum CompareDir {
Eq,
Lt,
Le,
Gt,
Ge,
}
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
pub struct GatherConfig {
pub offset_dims: Vec<usize>,
pub collapsed_slice_dims: Vec<usize>,
pub start_index_map: Vec<usize>,
pub index_vector_dim: usize,
pub slice_sizes: Vec<usize>,
}
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
pub struct ScatterConfig {
pub update_window_dims: Vec<usize>,
pub inserted_window_dims: Vec<usize>,
pub scatter_dims_to_operand_dims: Vec<usize>,
pub index_vector_dim: usize,
}
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
pub struct SliceConfig {
pub starts: Vec<usize>,
pub limits: Vec<usize>,
pub strides: Vec<usize>,
}
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
pub struct PadConfig {
pub edge_padding_low: Vec<i64>,
pub edge_padding_high: Vec<i64>,
pub interior_padding: Vec<i64>,
}