use crate::arrow::{arrow_to_tensor_block, TensorBlockArrowExt};
use crate::error::Result;
use crate::hash::global_hash_registry;
use crate::tensor::{TensorBlock, TensorShape};
use arrow_array::{ArrayRef, RecordBatch};
use arrow_schema::Schema;
use multihash_codetable::Code;
use std::sync::Arc;
pub struct TensorBatchProcessor {
hash_algo: Code,
}
impl TensorBatchProcessor {
pub fn new(hash_algo: Code) -> Self {
Self { hash_algo }
}
pub fn process_batch(&self, tensors: &[TensorBlock]) -> Result<Vec<String>> {
let registry = global_hash_registry();
let mut cids = Vec::with_capacity(tensors.len());
for tensor in tensors {
let data = tensor.data();
let _hash = registry.digest(self.hash_algo, data)?;
cids.push(tensor.cid().to_string());
}
Ok(cids)
}
pub fn to_arrow_batch(&self, tensors: Vec<(&str, &TensorBlock)>) -> Result<RecordBatch> {
let mut fields = Vec::new();
let mut arrays: Vec<ArrayRef> = Vec::new();
for (name, tensor) in tensors {
fields.push(tensor.to_arrow_field(name));
arrays.push(tensor.to_arrow_array()?);
}
let schema = Arc::new(Schema::new(fields));
RecordBatch::try_new(schema, arrays).map_err(|e| {
crate::error::Error::InvalidInput(format!("Failed to create RecordBatch: {}", e))
})
}
pub fn from_arrow_batch(
&self,
batch: &RecordBatch,
shapes: Vec<TensorShape>,
) -> Result<Vec<TensorBlock>> {
if batch.num_columns() != shapes.len() {
return Err(crate::error::Error::InvalidInput(format!(
"Column count {} doesn't match shape count {}",
batch.num_columns(),
shapes.len()
)));
}
let mut tensors = Vec::with_capacity(batch.num_columns());
for (col_idx, shape) in shapes.into_iter().enumerate() {
let array = batch.column(col_idx);
let tensor = arrow_to_tensor_block(array.as_ref(), shape)?;
tensors.push(tensor);
}
Ok(tensors)
}
}
impl Default for TensorBatchProcessor {
fn default() -> Self {
Self {
hash_algo: Code::Sha2_256,
}
}
}
pub struct TensorDeduplicator {
seen_cids: std::collections::HashMap<String, usize>,
}
impl TensorDeduplicator {
pub fn new() -> Self {
Self {
seen_cids: std::collections::HashMap::new(),
}
}
pub fn check(&mut self, tensor: &TensorBlock) -> Option<usize> {
let cid = tensor.cid().to_string();
self.seen_cids.get(&cid).copied()
}
pub fn register(&mut self, tensor: &TensorBlock) -> usize {
let cid = tensor.cid().to_string();
let idx = self.seen_cids.len();
self.seen_cids.entry(cid).or_insert(idx);
idx
}
pub fn unique_count(&self) -> usize {
self.seen_cids.len()
}
pub fn stats(&self) -> DeduplicationStats {
DeduplicationStats {
unique_tensors: self.seen_cids.len(),
total_checked: self.seen_cids.len(),
}
}
}
impl Default for TensorDeduplicator {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct DeduplicationStats {
pub unique_tensors: usize,
pub total_checked: usize,
}
impl DeduplicationStats {
pub fn dedup_ratio(&self) -> f64 {
if self.total_checked == 0 {
return 0.0;
}
self.unique_tensors as f64 / self.total_checked as f64
}
}
pub struct TensorStore {
tensors: std::collections::HashMap<String, TensorBlock>,
}
impl TensorStore {
pub fn new() -> Self {
Self {
tensors: std::collections::HashMap::new(),
}
}
pub fn store(&mut self, tensor: TensorBlock) -> String {
let cid = tensor.cid().to_string();
self.tensors.insert(cid.clone(), tensor);
cid
}
pub fn get(&self, cid: &str) -> Option<&TensorBlock> {
self.tensors.get(cid)
}
pub fn contains(&self, cid: &str) -> bool {
self.tensors.contains_key(cid)
}
pub fn len(&self) -> usize {
self.tensors.len()
}
pub fn is_empty(&self) -> bool {
self.tensors.is_empty()
}
pub fn list_cids(&self) -> Vec<String> {
self.tensors.keys().cloned().collect()
}
pub fn clear(&mut self) {
self.tensors.clear();
}
}
impl Default for TensorStore {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_batch_processor() {
let processor = TensorBatchProcessor::default();
let data1 = vec![1.0f32, 2.0, 3.0, 4.0];
let data2 = vec![5.0f32, 6.0, 7.0, 8.0];
let tensor1 = TensorBlock::from_f32_slice(&data1, TensorShape::new(vec![2, 2])).unwrap();
let tensor2 = TensorBlock::from_f32_slice(&data2, TensorShape::new(vec![2, 2])).unwrap();
let cids = processor.process_batch(&[tensor1, tensor2]).unwrap();
assert_eq!(cids.len(), 2);
assert_ne!(cids[0], cids[1]); }
#[test]
fn test_arrow_batch_roundtrip() {
let processor = TensorBatchProcessor::default();
let data1 = vec![1.0f32, 2.0, 3.0, 4.0];
let data2 = vec![5.0f32, 6.0, 7.0, 8.0];
let tensor1 = TensorBlock::from_f32_slice(&data1, TensorShape::new(vec![4])).unwrap();
let tensor2 = TensorBlock::from_f32_slice(&data2, TensorShape::new(vec![4])).unwrap();
let batch = processor
.to_arrow_batch(vec![("t1", &tensor1), ("t2", &tensor2)])
.unwrap();
assert_eq!(batch.num_columns(), 2);
assert_eq!(batch.num_rows(), 4);
let shapes = vec![TensorShape::new(vec![4]), TensorShape::new(vec![4])];
let recovered = processor.from_arrow_batch(&batch, shapes).unwrap();
assert_eq!(recovered.len(), 2);
assert_eq!(recovered[0].to_f32_vec().unwrap(), data1);
assert_eq!(recovered[1].to_f32_vec().unwrap(), data2);
}
#[test]
fn test_tensor_deduplicator() {
let mut dedup = TensorDeduplicator::new();
let data = vec![1.0f32, 2.0, 3.0, 4.0];
let tensor1 = TensorBlock::from_f32_slice(&data, TensorShape::new(vec![4])).unwrap();
let tensor2 = TensorBlock::from_f32_slice(&data, TensorShape::new(vec![4])).unwrap();
assert_eq!(dedup.check(&tensor1), None);
let idx1 = dedup.register(&tensor1);
assert_eq!(dedup.check(&tensor2), Some(idx1));
assert_eq!(dedup.unique_count(), 1);
}
#[test]
fn test_tensor_store() {
let mut store = TensorStore::new();
assert!(store.is_empty());
let data = vec![1.0f32, 2.0, 3.0, 4.0];
let tensor = TensorBlock::from_f32_slice(&data, TensorShape::new(vec![4])).unwrap();
let cid = store.store(tensor.clone());
assert_eq!(store.len(), 1);
assert!(store.contains(&cid));
let retrieved = store.get(&cid).unwrap();
assert_eq!(retrieved.to_f32_vec().unwrap(), data);
let cids = store.list_cids();
assert_eq!(cids.len(), 1);
assert_eq!(cids[0], cid);
store.clear();
assert!(store.is_empty());
}
#[test]
fn test_deduplication_stats() {
let mut dedup = TensorDeduplicator::new();
let data1 = vec![1.0f32, 2.0];
let data2 = vec![3.0f32, 4.0];
let t1 = TensorBlock::from_f32_slice(&data1, TensorShape::new(vec![2])).unwrap();
let t2 = TensorBlock::from_f32_slice(&data2, TensorShape::new(vec![2])).unwrap();
let t3 = TensorBlock::from_f32_slice(&data1, TensorShape::new(vec![2])).unwrap();
dedup.register(&t1);
dedup.register(&t2);
let _ = dedup.check(&t3);
let stats = dedup.stats();
assert_eq!(stats.unique_tensors, 2);
}
}