use std::sync::Arc;
use super::{bounded, history_index, DevicePlan, HostPlan, PagedAttentionError, WORKSPACE_LIMIT_BYTES};
use ruda_core::{device::Device, tensor::{DType, Shape}};
use ruda_kernel::{dsl::prelude::Runtime, tensor::{RudaTensor, allocation::empty_device_contiguous_dtype}};
pub struct OrderedBackwardWorkspace<R: Runtime> {
pub(super) statistics: RudaTensor<R>,
pub(super) index: RudaTensor<R>,
host: Arc<HostPlan>,
heads: usize,
bytes: usize,
pub(super) query_pruning: bool,
pub(super) history_row_cache: bool,
pub(super) compact_history: bool,
pub(super) page_partition: Option<RudaTensor<R>>,
pub(super) active_pages: usize,
}
impl<R: Runtime> OrderedBackwardWorkspace<R> {
pub fn new(plan: &DevicePlan<R>, like: &RudaTensor<R>) -> Result<Self, PagedAttentionError> {
if like.meta.num_dims()!=3 || like.meta.shape()[0]!=plan.host.queries || like.meta.shape()[1]==0
|| like.device.to_id()!=plan.metadata.device.to_id()
|| !like.client.same_execution_queue(&plan.metadata.client) {
return Err(PagedAttentionError("ordered workspace query shape/device/queue mismatch"));
}
let history_row_cache=super::history_row_cache::from_env()?;
let compact_history=super::history_compaction::from_env()?;
let heads=like.meta.shape()[1];
let elements=bounded(&[plan.host.queries,heads,3])?.max(1);
let stat_bytes=elements.checked_mul(4).filter(|&n| n<=WORKSPACE_LIMIT_BYTES)
.ok_or(PagedAttentionError("ordered statistics exceed workspace budget"))?;
let words=history_index::build(&plan.host,(WORKSPACE_LIMIT_BYTES-stat_bytes)/4)?;
let mut bytes=stat_bytes+words.len()*4;
let partition=if compact_history {
Some(super::history_compaction::build(&plan.host,(WORKSPACE_LIMIT_BYTES-bytes)/4)?)
} else { None };
let (page_partition,active_pages)=if let Some(partition)=partition {
bytes+=partition.pages.len()*4;
let map=RudaTensor::new_contiguous(like.client.clone(),like.device.clone(),Shape::from([partition.pages.len()]),
like.client.create_from_slice(bytemuck::cast_slice(&partition.pages)),DType::U32);
(Some(map),partition.active)
} else { (None,plan.host.pages) };
let index=RudaTensor::new_contiguous(like.client.clone(),like.device.clone(),Shape::from([words.len()]),
like.client.create_from_slice(bytemuck::cast_slice(&words)),DType::U32);
let statistics=empty_device_contiguous_dtype(like.client.clone(),like.device.clone(),Shape::from([elements]),DType::F32);
Ok(Self{statistics,index,host:plan.host.clone(),heads,bytes,query_pruning:true,history_row_cache,
compact_history,page_partition,active_pages})
}
pub fn set_query_pruning(&mut self, enabled: bool) { self.query_pruning = enabled; }
pub fn query_pruning(&self) -> bool { self.query_pruning }
pub fn set_history_row_cache(&mut self, enabled: bool) { self.history_row_cache=enabled; }
pub fn history_row_cache(&self) -> bool { self.history_row_cache }
pub fn set_history_compaction(&mut self, enabled: bool) -> Result<(), PagedAttentionError> {
if enabled && self.page_partition.is_none() {
let partition=super::history_compaction::build(&self.host,(WORKSPACE_LIMIT_BYTES-self.bytes)/4)?;
let map=RudaTensor::new_contiguous(self.index.client.clone(),self.index.device.clone(),
Shape::from([partition.pages.len()]),
self.index.client.create_from_slice(bytemuck::cast_slice(&partition.pages)),DType::U32);
self.bytes+=partition.pages.len()*4;
self.active_pages=partition.active;
self.page_partition=Some(map);
}
self.compact_history=enabled;
Ok(())
}
pub fn history_compaction(&self)->bool { self.compact_history }
pub fn history_compaction_pages(&self)->Option<(usize,usize)> {
self.page_partition.as_ref().map(|_| (self.active_pages,self.host.pages-self.active_pages))
}
pub fn bytes(&self)->usize { self.bytes }
pub fn statistics_bytes(&self)->usize { self.statistics.meta.num_elements()*4 }
pub fn matches(&self,plan:&DevicePlan<R>,like:&RudaTensor<R>)->bool {
like.meta.num_dims()==3 && like.meta.shape()[0]==self.host.queries
&& like.meta.shape()[1]==self.heads
&& (Arc::ptr_eq(&self.host, &plan.host) || self.host==plan.host)
&& self.index.device.to_id()==like.device.to_id()
&& self.index.client.same_execution_queue(&like.client)
}
}