use crate::want_list::Priority;
use ipfrs_core::Cid;
use std::collections::{HashMap, VecDeque};
use std::time::{Duration, Instant};
use tokio::sync::mpsc;
#[derive(Debug, Clone)]
pub struct TensorMetadata {
pub cid: Cid,
pub shape: Option<Vec<usize>>,
pub dtype: Option<String>,
pub dependencies: Vec<Cid>,
pub deadline: Option<Instant>,
pub size_bytes: Option<u64>,
pub chunks: Vec<Cid>,
pub priority_hint: Option<i32>,
pub layer_name: Option<String>,
pub is_critical: bool,
}
impl TensorMetadata {
pub fn new(cid: Cid) -> Self {
Self {
cid,
shape: None,
dtype: None,
dependencies: Vec::new(),
deadline: None,
size_bytes: None,
chunks: Vec::new(),
priority_hint: None,
layer_name: None,
is_critical: false,
}
}
pub fn with_shape(mut self, shape: Vec<usize>) -> Self {
self.shape = Some(shape);
self
}
pub fn with_dtype(mut self, dtype: impl Into<String>) -> Self {
self.dtype = Some(dtype.into());
self
}
pub fn with_dependencies(mut self, deps: Vec<Cid>) -> Self {
self.dependencies = deps;
self
}
pub fn with_deadline(mut self, deadline: Instant) -> Self {
self.deadline = Some(deadline);
self
}
pub fn with_size(mut self, size: u64) -> Self {
self.size_bytes = Some(size);
self
}
pub fn with_chunks(mut self, chunks: Vec<Cid>) -> Self {
self.chunks = chunks;
self
}
pub fn with_priority_hint(mut self, priority: i32) -> Self {
self.priority_hint = Some(priority);
self
}
pub fn with_layer_name(mut self, name: impl Into<String>) -> Self {
self.layer_name = Some(name.into());
self
}
pub fn critical(mut self) -> Self {
self.is_critical = true;
self
}
pub fn num_elements(&self) -> Option<usize> {
self.shape.as_ref().map(|s| s.iter().product())
}
pub fn estimated_size(&self) -> Option<u64> {
if let Some(size) = self.size_bytes {
return Some(size);
}
let elements = self.num_elements()? as u64;
let bytes_per_element = match self.dtype.as_deref() {
Some("f32") | Some("i32") | Some("u32") => 4,
Some("f64") | Some("i64") | Some("u64") => 8,
Some("f16") | Some("bf16") | Some("i16") | Some("u16") => 2,
Some("i8") | Some("u8") | Some("bool") => 1,
_ => return None,
};
Some(elements * bytes_per_element)
}
pub fn is_chunked(&self) -> bool {
!self.chunks.is_empty()
}
}
#[derive(Debug, Clone)]
pub struct ChunkInfo {
pub cid: Cid,
pub index: usize,
pub offset: u64,
pub size: u64,
pub received: bool,
}
#[derive(Debug, Clone)]
pub struct StreamProgress {
pub root_cid: Cid,
pub chunk_index: usize,
pub chunk_cid: Cid,
pub total_chunks: usize,
pub bytes_received: u64,
pub total_bytes: Option<u64>,
pub complete: bool,
}
#[derive(Debug)]
pub struct TensorStream {
pub root_cid: Cid,
pub metadata: TensorMetadata,
pub chunks: Vec<ChunkInfo>,
pub chunks_received: usize,
pub bytes_received: u64,
pub started_at: Instant,
progress_tx: Option<mpsc::Sender<StreamProgress>>,
}
impl TensorStream {
pub fn new(metadata: TensorMetadata) -> Self {
let chunks: Vec<ChunkInfo> = if metadata.is_chunked() {
let chunk_size = metadata
.size_bytes
.map(|s| s / metadata.chunks.len() as u64)
.unwrap_or(1024 * 1024);
metadata
.chunks
.iter()
.enumerate()
.map(|(i, cid)| ChunkInfo {
cid: *cid,
index: i,
offset: i as u64 * chunk_size,
size: chunk_size,
received: false,
})
.collect()
} else {
vec![ChunkInfo {
cid: metadata.cid,
index: 0,
offset: 0,
size: metadata.size_bytes.unwrap_or(0),
received: false,
}]
};
Self {
root_cid: metadata.cid,
metadata,
chunks,
chunks_received: 0,
bytes_received: 0,
started_at: Instant::now(),
progress_tx: None,
}
}
pub fn with_progress_channel(mut self, tx: mpsc::Sender<StreamProgress>) -> Self {
self.progress_tx = Some(tx);
self
}
pub async fn mark_received(&mut self, cid: &Cid, size: u64) -> bool {
if let Some(chunk) = self.chunks.iter_mut().find(|c| c.cid == *cid) {
if !chunk.received {
chunk.received = true;
chunk.size = size;
self.chunks_received += 1;
self.bytes_received += size;
if let Some(tx) = &self.progress_tx {
let progress = StreamProgress {
root_cid: self.root_cid,
chunk_index: chunk.index,
chunk_cid: *cid,
total_chunks: self.chunks.len(),
bytes_received: self.bytes_received,
total_bytes: self.metadata.size_bytes,
complete: self.is_complete(),
};
let _ = tx.send(progress).await;
}
return true;
}
}
false
}
pub fn is_complete(&self) -> bool {
self.chunks_received >= self.chunks.len()
}
pub fn progress(&self) -> f64 {
if self.chunks.is_empty() {
return 1.0;
}
self.chunks_received as f64 / self.chunks.len() as f64
}
pub fn missing_chunks(&self) -> Vec<Cid> {
self.chunks
.iter()
.filter(|c| !c.received)
.map(|c| c.cid)
.collect()
}
pub fn elapsed(&self) -> Duration {
self.started_at.elapsed()
}
pub fn throughput(&self) -> f64 {
let elapsed = self.elapsed().as_secs_f64();
if elapsed > 0.0 {
self.bytes_received as f64 / elapsed
} else {
0.0
}
}
}
#[derive(Debug, Clone)]
pub struct BackpressureConfig {
pub max_pending: usize,
pub high_watermark: usize,
pub low_watermark: usize,
pub max_buffer_bytes: usize,
}
impl Default for BackpressureConfig {
fn default() -> Self {
Self {
max_pending: 64,
high_watermark: 48,
low_watermark: 16,
max_buffer_bytes: 64 * 1024 * 1024, }
}
}
#[derive(Debug)]
pub struct BackpressureController {
config: BackpressureConfig,
pending_count: usize,
pending_bytes: usize,
paused: bool,
}
impl BackpressureController {
pub fn new(config: BackpressureConfig) -> Self {
Self {
config,
pending_count: 0,
pending_bytes: 0,
paused: false,
}
}
pub fn should_accept(&self) -> bool {
!self.paused
&& self.pending_count < self.config.max_pending
&& self.pending_bytes < self.config.max_buffer_bytes
}
pub fn on_send(&mut self, bytes: usize) {
self.pending_count += 1;
self.pending_bytes += bytes;
if self.pending_count >= self.config.high_watermark
|| self.pending_bytes >= self.config.max_buffer_bytes
{
self.paused = true;
}
}
pub fn on_ack(&mut self, bytes: usize) {
self.pending_count = self.pending_count.saturating_sub(1);
self.pending_bytes = self.pending_bytes.saturating_sub(bytes);
if self.pending_count <= self.config.low_watermark {
self.paused = false;
}
}
pub fn is_paused(&self) -> bool {
self.paused
}
pub fn pending_count(&self) -> usize {
self.pending_count
}
pub fn pending_bytes(&self) -> usize {
self.pending_bytes
}
pub fn reset(&mut self) {
self.pending_count = 0;
self.pending_bytes = 0;
self.paused = false;
}
}
#[derive(Debug, Clone)]
pub struct SafetensorsHeader {
pub header_size: u64,
pub tensors: HashMap<String, SafetensorEntry>,
}
#[derive(Debug, Clone)]
pub struct SafetensorEntry {
pub name: String,
pub dtype: String,
pub shape: Vec<usize>,
pub data_offset: u64,
pub data_length: u64,
}
impl SafetensorsHeader {
pub fn parse(data: &[u8]) -> ipfrs_core::Result<Self> {
if data.len() < 8 {
return Err(ipfrs_core::Error::Deserialization(
"Safetensors header too short".to_string(),
));
}
let header_size =
u64::from_le_bytes(data[0..8].try_into().map_err(|_| {
ipfrs_core::Error::Deserialization("header size bytes".to_string())
})?);
if data.len() < 8 + header_size as usize {
return Err(ipfrs_core::Error::Deserialization(
"Safetensors data too short for header".to_string(),
));
}
let header_json = &data[8..8 + header_size as usize];
let header_map: HashMap<String, serde_json::Value> = serde_json::from_slice(header_json)
.map_err(|e| {
ipfrs_core::Error::Deserialization(format!("Invalid safetensors header: {}", e))
})?;
let mut tensors = HashMap::new();
for (name, value) in header_map {
if name == "__metadata__" {
continue;
}
if let Some(obj) = value.as_object() {
let dtype = obj
.get("dtype")
.and_then(|v| v.as_str())
.unwrap_or("F32")
.to_string();
let shape: Vec<usize> = obj
.get("shape")
.and_then(|v| v.as_array())
.map(|arr| {
arr.iter()
.filter_map(|v| v.as_u64().map(|n| n as usize))
.collect()
})
.unwrap_or_default();
let offsets = obj.get("data_offsets").and_then(|v| v.as_array());
let (data_offset, data_length) = if let Some(offs) = offsets {
let start = offs.first().and_then(|v| v.as_u64()).unwrap_or(0);
let end = offs.get(1).and_then(|v| v.as_u64()).unwrap_or(start);
(start, end - start)
} else {
(0, 0)
};
tensors.insert(
name.clone(),
SafetensorEntry {
name,
dtype,
shape,
data_offset,
data_length,
},
);
}
}
Ok(Self {
header_size,
tensors,
})
}
pub fn data_start(&self) -> u64 {
8 + self.header_size
}
pub fn get_tensor(&self, name: &str) -> Option<&SafetensorEntry> {
self.tensors.get(name)
}
pub fn tensor_names(&self) -> Vec<&str> {
self.tensors.keys().map(|s| s.as_str()).collect()
}
}
#[derive(Debug)]
pub struct StreamRequestQueue {
requests: VecDeque<StreamRequest>,
max_size: usize,
}
#[derive(Debug, Clone)]
pub struct StreamRequest {
pub cid: Cid,
pub priority: i32,
pub deadline: Option<Instant>,
pub queued_at: Instant,
}
impl StreamRequestQueue {
pub fn new(max_size: usize) -> Self {
Self {
requests: VecDeque::with_capacity(max_size),
max_size,
}
}
pub fn push(&mut self, request: StreamRequest) -> bool {
if self.requests.len() >= self.max_size {
return false;
}
let pos = self
.requests
.iter()
.position(|r| r.priority < request.priority)
.unwrap_or(self.requests.len());
self.requests.insert(pos, request);
true
}
pub fn pop(&mut self) -> Option<StreamRequest> {
self.requests.pop_front()
}
pub fn peek(&self) -> Option<&StreamRequest> {
self.requests.front()
}
pub fn is_empty(&self) -> bool {
self.requests.is_empty()
}
pub fn len(&self) -> usize {
self.requests.len()
}
pub fn boost_deadlines(&mut self) {
let now = Instant::now();
for request in &mut self.requests {
if let Some(deadline) = request.deadline {
if now >= deadline {
request.priority = request.priority.max(Priority::Critical as i32);
} else if deadline.duration_since(now) < Duration::from_secs(1) {
request.priority = request.priority.max(Priority::Urgent as i32);
}
}
}
let mut vec: Vec<_> = self.requests.drain(..).collect();
vec.sort_by_key(|v| std::cmp::Reverse(v.priority));
self.requests = vec.into();
}
}