From b6a9d52e1937aa656c8ff8a8df723489f5981c71 Mon Sep 17 00:00:00 2001 From: Brandon Thomas Date: Tue, 25 Feb 2025 00:25:19 -0500 Subject: [PATCH] implement cumprod --- crates/desktop/src-tauri/src/ml/cumprod.rs | 150 +++++++++++++++++++++ crates/desktop/src-tauri/src/ml/mod.rs | 1 + 2 files changed, 151 insertions(+) create mode 100644 crates/desktop/src-tauri/src/ml/cumprod.rs diff --git a/crates/desktop/src-tauri/src/ml/cumprod.rs b/crates/desktop/src-tauri/src/ml/cumprod.rs new file mode 100644 index 0000000000..33679908af --- /dev/null +++ b/crates/desktop/src-tauri/src/ml/cumprod.rs @@ -0,0 +1,150 @@ +use candle_core::shape::Dim; +use candle_core::Tensor; + +/// Tensor Cumulative Product +/// +/// Candle doesn't implement cumprod, only cumsum. +/// This implementation is adapted from cumsum in candle-ext: +/// https://github.com/mokeyish/candle-ext/blob/main/tests/cumsum.rs +/// +/// See Also: https://pytorch.org/docs/stable/generated/torch.cumprod.html +/// See Also: https://numpy.org/doc/stable/reference/generated/numpy.cumprod.html +/// See Also: https://github.com/huggingface/candle/issues/1646 +/// +pub fn cumprod(input: &Tensor, dim: D) -> candle_core::Result { + let dim = dim.to_index(input.shape(), "cumprod")?; + let dim_size = input.dim(dim)?; + let rank = input.rank(); + if rank == 0 { + return Ok(input.clone()); + } + + let mut tensors = Vec::with_capacity(dim_size); + + let mut a = input.clone(); + for i in 0..dim_size { + if i > 0 { + a = a.narrow(dim, 1, dim_size - i)?; + let b = input.narrow(dim, 0, dim_size - i)?; + a = (a * b)?; + } + tensors.push(a.narrow(dim, 0, 1)?); + } + + let cumsum = Tensor::cat(&tensors, dim)?; + Ok(cumsum) +} + +#[cfg(test)] +mod tests { + use crate::ml::cumprod::cumprod; + use candle_core::{Device, Tensor}; + + mod base_cases { + use super::*; + + #[test] + fn zeroes_vector() -> anyhow::Result<()> { + let a = Tensor::new(&[0i64, 0, 0, 0], &Device::Cpu)?; + let b = cumprod(&a, 0)?; + assert_eq!(b.to_vec1::()?, &[0, 0, 0, 0]); + Ok(()) + } + + #[test] + fn zeroes_tensor() -> anyhow::Result<()> { + let a = Tensor::new(&[ + [0i64, 0, 0], + [0i64, 0, 0], + [0i64, 0, 0], + ], &Device::Cpu)?; + + let b = cumprod(&a, 0)?; + + assert_eq!(b.to_vec2::()?, &[ + [0, 0, 0], + [0, 0, 0], + [0, 0, 0], + ]); + Ok(()) + } + + #[test] + fn ones_vector() -> anyhow::Result<()> { + let a = Tensor::new(&[1i64, 1, 1, 1], &Device::Cpu)?; + let b = cumprod(&a, 0)?; + assert_eq!(b.to_vec1::()?, &[1, 1, 1, 1]); + Ok(()) + } + + #[test] + fn ones_tensor() -> anyhow::Result<()> { + let a = Tensor::new(&[ + [1i64, 1, 1], + [1i64, 1, 1], + [1i64, 1, 1], + ], &Device::Cpu)?; + + let b = cumprod(&a, 0)?; + + assert_eq!(b.to_vec2::()?, &[ + [1, 1, 1], + [1, 1, 1], + [1, 1, 1], + ]); + Ok(()) + } + } + + mod test_cases { + use super::*; + + #[test] + fn vector() -> anyhow::Result<()> { + let a = Tensor::new(&[1i64, 2, 3, 4, 5], &Device::Cpu)?; + let b = cumprod(&a, 0)?; + assert_eq!(b.to_vec1::()?, &[1, 2, 6, 24, 120]); + + let a = Tensor::new(&[0, 1i64, 2, 3, 4, 5], &Device::Cpu)?; + let b = cumprod(&a, 0)?; + assert_eq!(b.to_vec1::()?, &[0, 0, 0, 0, 0, 0]); + Ok(()) + } + + #[test] + fn tensor_dim_0() -> anyhow::Result<()> { + let a = Tensor::new(&[ + [1i64, 2, 3], + [4i64, 5, 6], + [7i64, 8, 9], + ], &Device::Cpu)?; + + let b = cumprod(&a, 0)?; + + assert_eq!(b.to_vec2::()?, &[ + [1, 2, 3], + [4, 10, 18], + [28, 80, 162], + ]); + Ok(()) + } + + #[test] + fn tensor_dim_1() -> anyhow::Result<()> { + let a = Tensor::new(&[ + [1i64, 2, 3], + [4i64, 5, 6], + [7i64, 8, 9], + ], &Device::Cpu)?; + + let b = cumprod(&a, 1)?; + + assert_eq!(b.to_vec2::()?, &[ + [1, 2, 6], + [4, 20, 120], + [7, 56, 504], + ]); + Ok(()) + } + } +} diff --git a/crates/desktop/src-tauri/src/ml/mod.rs b/crates/desktop/src-tauri/src/ml/mod.rs index b3563b045e..ee9573093f 100644 --- a/crates/desktop/src-tauri/src/ml/mod.rs +++ b/crates/desktop/src-tauri/src/ml/mod.rs @@ -1,3 +1,4 @@ +pub mod cumprod; pub mod image; pub mod lcm_scheduler; pub mod model_cache;