use core::fmt;
use std::fmt::Formatter;
use burn_tensor::DType;
use super::tensor_data_ext::TensorData;
use crate::tensor_store::ValueStore;
pub type Rank = usize;
pub type Shape = Vec<Option<usize>>;
pub type DataId = usize;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ValueSource {
Static(DataId),
Constant,
Dynamic,
Optional,
}
#[derive(Clone)]
pub struct Argument {
pub name: String,
pub ty: ArgType,
pub value_source: ValueSource,
pub(crate) value_store: Option<ValueStore>,
}
impl fmt::Debug for Argument {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
f.debug_struct("Argument")
.field("name", &self.name)
.field("ty", &self.ty)
.field("value_source", &self.value_source)
.finish()
}
}
impl fmt::Display for TensorType {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(f, "{:?}[", self.dtype)?;
match &self.static_shape {
Some(dims) => {
for (i, dim) in dims.iter().enumerate() {
if i > 0 {
write!(f, ", ")?;
}
match dim {
Some(val) => write!(f, "{val}")?,
None => write!(f, "?")?,
}
}
}
None => {
for i in 0..self.rank {
if i > 0 {
write!(f, ", ")?;
}
write!(f, "?")?;
}
}
}
write!(f, "]")
}
}
impl fmt::Display for ArgType {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
match self {
ArgType::ScalarTensor(dtype) => write!(f, "ScalarTensor({dtype:?})"),
ArgType::ScalarNative(dtype) => write!(f, "ScalarNative({dtype:?})"),
ArgType::Shape(rank) => write!(f, "Shape({rank})"),
ArgType::Tensor(t) => write!(f, "{t}"),
}
}
}
impl fmt::Display for ValueSource {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
match self {
ValueSource::Dynamic => Ok(()),
ValueSource::Static(id) => write!(f, " [static({id})]"),
ValueSource::Constant => write!(f, " [constant]"),
ValueSource::Optional => write!(f, "<optional>"),
}
}
}
impl fmt::Display for Argument {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
if self.is_optional() {
return write!(f, "<optional>");
}
let name = if self.name.is_empty() {
"_"
} else {
&self.name
};
write!(f, "{name}: {}{}", self.ty, self.value_source)
}
}
impl Argument {
pub fn copy_value(&mut self, other_arg: &Argument) {
self.ty = other_arg.ty.clone();
self.value_source = other_arg.value_source;
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum ArgType {
ScalarTensor(DType),
ScalarNative(DType),
Shape(Rank),
Tensor(TensorType),
}
#[derive(Debug, Clone, PartialEq)]
pub struct TensorType {
pub dtype: DType,
pub rank: Rank,
pub static_shape: Option<Vec<Option<usize>>>,
}
impl Default for TensorType {
fn default() -> Self {
Self {
dtype: DType::F32,
rank: 0,
static_shape: None,
}
}
}
impl TensorType {
pub fn new(dtype: DType, rank: Rank, static_shape: Option<Vec<Option<usize>>>) -> Self {
Self {
dtype,
rank,
static_shape,
}
}
pub fn new_known(dtype: DType, shape: Vec<usize>) -> Self {
let rank = shape.len();
Self {
dtype,
rank,
static_shape: Some(shape.into_iter().map(Some).collect()),
}
}
pub fn static_shape_known(&self) -> Option<Vec<usize>> {
self.static_shape
.as_ref()
.and_then(|dims| dims.iter().copied().collect::<Option<Vec<usize>>>())
}
pub fn merge_static_shape(&mut self, other: &TensorType) -> bool {
if self.rank != other.rank {
return false;
}
match (&mut self.static_shape, &other.static_shape) {
(None, Some(other_shape)) if other_shape.len() == self.rank => {
self.static_shape = Some(other_shape.clone());
true
}
(Some(self_shape), Some(other_shape)) if self_shape.len() == other_shape.len() => {
let mut changed = false;
for (self_dim, other_dim) in self_shape.iter_mut().zip(other_shape.iter()) {
if self_dim.is_none() && other_dim.is_some() {
*self_dim = *other_dim;
changed = true;
}
}
changed
}
_ => false,
}
}
}
impl Default for ArgType {
fn default() -> Self {
Self::Tensor(TensorType::default())
}
}
impl ArgType {
pub fn merge_static_shape(&mut self, other: &ArgType) -> bool {
match (self, other) {
(ArgType::Tensor(self_tensor), ArgType::Tensor(other_tensor)) => {
self_tensor.merge_static_shape(other_tensor)
}
_ => false,
}
}
pub fn is_scalar(&self) -> bool {
matches!(self, Self::ScalarTensor(_) | Self::ScalarNative(_))
}
pub fn is_scalar_tensor(&self) -> bool {
matches!(self, Self::ScalarTensor(_))
}
pub fn is_scalar_native(&self) -> bool {
matches!(self, Self::ScalarNative(_))
}
pub fn is_on_device(&self) -> bool {
matches!(self, Self::Tensor(_) | Self::ScalarTensor(_))
}
pub fn is_tensor(&self) -> bool {
matches!(self, Self::Tensor(_))
}
pub fn is_shape(&self) -> bool {
matches!(self, Self::Shape(_))
}
pub fn rank(&self) -> usize {
match self {
ArgType::ScalarTensor(_) => 1,
ArgType::ScalarNative(_) => 0,
ArgType::Shape(_) => 1,
ArgType::Tensor(t) => t.rank,
}
}
pub fn elem_type(&self) -> DType {
match self {
ArgType::ScalarTensor(d) | ArgType::ScalarNative(d) => *d,
ArgType::Shape(_) => panic!("ArgType::Shape has no DType"),
ArgType::Tensor(t) => t.dtype,
}
}
pub fn static_shape(&self) -> Option<&Vec<Option<usize>>> {
match self {
ArgType::Tensor(t) => t.static_shape.as_ref(),
_ => None,
}
}
pub fn static_shape_known(&self) -> Option<Vec<usize>> {
match self {
ArgType::Tensor(t) => t.static_shape_known(),
_ => None,
}
}
}
impl Argument {
pub fn new(name: impl Into<String>, ty: ArgType) -> Self {
let name = name.into();
let value_source = if name.is_empty() {
ValueSource::Optional
} else {
ValueSource::Dynamic
};
Self {
name,
ty,
value_source,
value_store: None,
}
}
pub fn from_name(name: impl Into<String>) -> Self {
Self::new(name, ArgType::default())
}
pub fn value(&self) -> Option<TensorData> {
if self.value_store.is_none() {
if matches!(
self.value_source,
ValueSource::Constant | ValueSource::Static(_)
) {
log::warn!(
"value() called on '{}' (value_source={:?}) but value_store is None",
self.name,
self.value_source
);
}
return None;
}
let store = self.value_store.as_ref()?;
match &self.value_source {
ValueSource::Static(data_id) => {
let result = store.get_tensor_data(*data_id);
if result.is_none() {
log::warn!(
"value() for Static({}) on '{}' returned None - store has {} tensors",
data_id,
self.name,
store.tensor_count()
);
}
result
}
ValueSource::Constant => {
let data_id = store.get_constant_data_id(&self.name);
if data_id.is_none() {
log::warn!(
"value() lookup failed for '{}': constant not found in store (constant_map has {} entries)",
self.name,
store.constant_map_len()
);
}
let data_id = data_id?;
store.get_tensor_data(data_id)
}
ValueSource::Dynamic | ValueSource::Optional => None,
}
}
pub(crate) fn set_value_store(&mut self, store: ValueStore) {
self.value_store = Some(store);
}
pub fn is_static(&self) -> bool {
matches!(self.value_source, ValueSource::Static(_))
}
pub fn is_constant(&self) -> bool {
self.value_source == ValueSource::Constant
}
pub fn is_dynamic(&self) -> bool {
self.value_source == ValueSource::Dynamic
}
pub fn is_optional(&self) -> bool {
self.value_source == ValueSource::Optional
}
pub fn to_static(&mut self) -> Result<(), crate::processor::ProcessError> {
use crate::processor::ProcessError;
if !self.is_constant() {
return Err(ProcessError::Custom(format!(
"Cannot convert {:?} argument to Static (only Constant can be converted)",
self.value_source
)));
}
let store = self.value_store.as_ref().ok_or_else(|| {
ProcessError::Custom("No value store available to look up constant".to_string())
})?;
let data_id = store.get_constant_data_id(&self.name).ok_or_else(|| {
ProcessError::Custom(format!(
"Constant node not found or has no data_id for output name: {}",
self.name
))
})?;
self.value_source = ValueSource::Static(data_id);
self.name.clear();
Ok(())
}
pub fn from_const_i64(name: impl Into<String>, value: i64) -> Self {
Self::from_const_i64s(name, &[value], ArgType::ScalarNative(DType::I64))
}
pub fn from_const_i64_shape(name: impl Into<String>, values: &[i64]) -> Self {
Self::from_const_i64s(name, values, ArgType::Shape(values.len()))
}
fn from_const_i64s(name: impl Into<String>, values: &[i64], ty: ArgType) -> Self {
use crate::tensor_store::{TensorDataRef, TensorStore, ValueStore};
use std::collections::HashMap;
use std::sync::Arc;
let bytes: Vec<u8> = values.iter().flat_map(|v| v.to_ne_bytes()).collect();
let shape = if values.len() == 1 && ty.is_scalar() {
vec![]
} else {
vec![values.len()]
};
let data_ref = TensorDataRef::new(bytes::Bytes::from(bytes), shape, DType::I64);
let mut tensor_store = TensorStore::new();
let data_id = tensor_store.store(data_ref);
let value_store = ValueStore::new(Arc::new(tensor_store), Arc::new(HashMap::new()));
Self {
name: name.into(),
ty,
value_source: ValueSource::Static(data_id),
value_store: Some(value_store),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_merge_static_shape_none_with_some() {
let mut inferred = TensorType::new(DType::F32, 2, None);
let value_info = TensorType::new(DType::F32, 2, Some(vec![None, Some(1)]));
let changed = inferred.merge_static_shape(&value_info);
assert!(changed);
assert_eq!(inferred.static_shape, Some(vec![None, Some(1)]));
}
#[test]
fn test_merge_static_shape_some_with_none() {
let mut inferred = TensorType::new(DType::F32, 2, Some(vec![Some(5), None]));
let value_info = TensorType::new(DType::F32, 2, None);
let changed = inferred.merge_static_shape(&value_info);
assert!(!changed);
assert_eq!(inferred.static_shape, Some(vec![Some(5), None]));
}
#[test]
fn test_merge_static_shape_dimension_by_dimension() {
let mut inferred = TensorType::new(DType::F32, 2, Some(vec![Some(5), None]));
let value_info = TensorType::new(DType::F32, 2, Some(vec![None, Some(1)]));
let changed = inferred.merge_static_shape(&value_info);
assert!(changed);
assert_eq!(inferred.static_shape, Some(vec![Some(5), Some(1)]));
}
#[test]
fn test_merge_static_shape_no_change_when_same() {
let mut inferred = TensorType::new(DType::F32, 2, Some(vec![Some(5), Some(1)]));
let value_info = TensorType::new(DType::F32, 2, Some(vec![Some(5), Some(1)]));
let changed = inferred.merge_static_shape(&value_info);
assert!(!changed);
assert_eq!(inferred.static_shape, Some(vec![Some(5), Some(1)]));
}
#[test]
fn test_merge_static_shape_inferred_takes_precedence_when_both_known() {
let mut inferred = TensorType::new(DType::F32, 2, Some(vec![Some(5), Some(2)]));
let value_info = TensorType::new(DType::F32, 2, Some(vec![Some(10), Some(1)]));
let changed = inferred.merge_static_shape(&value_info);
assert!(!changed);
assert_eq!(inferred.static_shape, Some(vec![Some(5), Some(2)]));
}
#[test]
fn test_merge_static_shape_rank_mismatch_no_merge() {
let mut inferred = TensorType::new(DType::F32, 3, None);
let value_info = TensorType::new(DType::F32, 2, Some(vec![None, Some(1)]));
let changed = inferred.merge_static_shape(&value_info);
assert!(!changed);
assert_eq!(inferred.static_shape, None);
}
#[test]
fn test_merge_argtype_tensor() {
let mut inferred =
ArgType::Tensor(TensorType::new(DType::F32, 2, Some(vec![Some(5), None])));
let value_info = ArgType::Tensor(TensorType::new(DType::F32, 2, Some(vec![None, Some(1)])));
let changed = inferred.merge_static_shape(&value_info);
assert!(changed);
assert_eq!(
inferred,
ArgType::Tensor(TensorType::new(DType::F32, 2, Some(vec![Some(5), Some(1)])))
);
}
#[test]
fn test_merge_argtype_non_tensor_no_op() {
let mut inferred = ArgType::ScalarNative(DType::F32);
let value_info = ArgType::ScalarNative(DType::F32);
let changed = inferred.merge_static_shape(&value_info);
assert!(!changed);
}
#[test]
fn test_display_tensor_type_known_shape() {
let t = TensorType::new(DType::F32, 3, Some(vec![Some(2), Some(3), Some(4)]));
assert_eq!(format!("{t}"), "F32[2, 3, 4]");
}
#[test]
fn test_display_tensor_type_partial_shape() {
let t = TensorType::new(DType::I64, 3, Some(vec![None, Some(3), None]));
assert_eq!(format!("{t}"), "I64[?, 3, ?]");
}
#[test]
fn test_display_tensor_type_no_shape() {
let t = TensorType::new(DType::F32, 2, None);
assert_eq!(format!("{t}"), "F32[?, ?]");
}
#[test]
fn test_display_argtype_scalar() {
let a = ArgType::ScalarNative(DType::F32);
assert_eq!(format!("{a}"), "ScalarNative(F32)");
let b = ArgType::ScalarTensor(DType::F32);
assert_eq!(format!("{b}"), "ScalarTensor(F32)");
}
#[test]
fn test_display_argtype_shape() {
let a = ArgType::Shape(3);
assert_eq!(format!("{a}"), "Shape(3)");
}
#[test]
fn test_display_argument_dynamic() {
let arg = Argument::new(
"input",
ArgType::Tensor(TensorType::new(DType::F32, 2, Some(vec![Some(2), Some(3)]))),
);
assert_eq!(format!("{arg}"), "input: F32[2, 3]");
}
#[test]
fn test_display_argument_static() {
let mut arg = Argument::new(
"",
ArgType::Tensor(TensorType::new_known(DType::F32, vec![16])),
);
arg.value_source = ValueSource::Static(0);
assert_eq!(format!("{arg}"), "_: F32[16] [static(0)]");
}
#[test]
fn test_display_argument_constant() {
let mut arg = Argument::new(
"bias",
ArgType::Tensor(TensorType::new_known(DType::F32, vec![16])),
);
arg.value_source = ValueSource::Constant;
assert_eq!(format!("{arg}"), "bias: F32[16] [constant]");
}
#[test]
fn test_display_argument_optional() {
let arg = Argument::new("", ArgType::default());
assert_eq!(format!("{arg}"), "<optional>");
}
}