use crate::device::get_context;
use crate::fact::DeviceTypedFactExt;
use crate::rule_ensure;
use crate::tensor::{DeviceTensor, DeviceTensorExt, IntoDevice};
use derive_new::new;
use tract_core::internal::*;
use tract_transformers::ops::dyn_kv_cache::{DynKeyValueCache, DynKeyValueCacheState};
#[derive(Debug, Clone, new)]
pub struct GpuDynKVCacheState {
name: String,
axis: usize,
past_sequence_fact: TypedFact,
#[new(default)]
buffer: Option<DeviceTensor>,
#[new(default)]
len: usize,
#[new(default)]
reallocs: usize,
#[new(default)]
window_output: bool,
}
impl GpuDynKVCacheState {
pub fn capacity(&self) -> usize {
self.buffer.as_ref().map(|b| b.shape()[self.axis]).unwrap_or(0)
}
fn valid_shape(&self) -> Option<TVec<usize>> {
let mut shape: TVec<usize> = self.buffer.as_ref()?.shape().into();
shape[self.axis] = self.len;
Some(shape)
}
fn valid(&self) -> TractResult<DeviceTensor> {
let buffer = self.buffer.as_ref().context("KV cache was never initialized")?;
let shape = self.valid_shape().unwrap();
let out = DeviceTensor::uninitialized_dt(buffer.datum_type(), &shape)?;
get_context()?.assign_slice(&out, 0..self.len, buffer, 0..self.len, self.axis)?;
Ok(out)
}
fn push(&mut self, input: &DeviceTensor) -> TractResult<()> {
let new = input.shape()[self.axis];
if new == 0 {
return Ok(());
}
if self.len + new > self.capacity() {
self.grow(self.len + new, input)?;
}
let buffer = self.buffer.as_ref().unwrap();
get_context()?.assign_slice(buffer, self.len..self.len + new, input, 0..new, self.axis)?;
self.len += new;
Ok(())
}
fn grow(&mut self, needed: usize, template: &DeviceTensor) -> TractResult<()> {
let mut shape: TVec<usize> = self
.buffer
.as_ref()
.map(|b| b.shape().into())
.unwrap_or_else(|| template.shape().into());
shape[self.axis] = (self.capacity() * 2).max(needed);
let grown = DeviceTensor::uninitialized_dt(template.datum_type(), &shape)?;
if let Some(buffer) = self.buffer.as_ref() {
get_context()?.assign_slice(&grown, 0..self.len, buffer, 0..self.len, self.axis)?;
}
self.buffer = Some(grown);
self.reallocs += 1;
Ok(())
}
}
impl OpState for GpuDynKVCacheState {
fn load_from(
&mut self,
state: &mut TurnState,
states: &mut dyn Iterator<Item = TValue>,
) -> TractResult<()> {
let kv_cache = states.next().context("Not enough state initializers")?;
DynKeyValueCacheState::resolve_symbols(
state,
self.past_sequence_fact.clone(),
Some(kv_cache.shape()),
)?;
self.len = kv_cache.shape()[self.axis];
self.buffer = Some(kv_cache.into_tensor().into_device()?);
Ok(())
}
fn save_to(&self, states: &mut Vec<TValue>) -> TractResult<()> {
if self.buffer.is_some() {
states.push(self.valid()?.to_host()?.into_tensor().into_tvalue());
Ok(())
} else {
bail!("KV cache {} was never initialized", self.name)
}
}
fn init_tensor_fact(&self) -> Option<(String, TypedFact)> {
Some((self.name.clone(), self.past_sequence_fact.clone()))
}
fn has_init_tensor_fact(&self) -> bool {
true
}
fn resolve_symbols(&mut self, state: &mut TurnState) -> TractResult<()> {
let shape = self.valid_shape();
DynKeyValueCacheState::resolve_symbols(
state,
self.past_sequence_fact.clone(),
shape.as_deref(),
)
}
fn eval(
&mut self,
_ctx: &EvalContext,
_op: &dyn Op,
inputs: TVec<TValue>,
) -> TractResult<TVec<TValue>> {
ensure!(inputs.len() == 1);
let input = inputs.into_iter().next().unwrap();
self.push(input.to_device_tensor()?)?;
let out = if self.window_output {
let buffer = self.buffer.as_ref().unwrap();
buffer.prefix_window(self.axis, self.len)?
} else {
self.valid()?
};
Ok(tvec!(out.into_tensor().into_tvalue()))
}
fn reset_lanes(&mut self, _lanes: &[LaneId]) -> TractResult<()> {
bail!("GpuDynKVCache is not lane-aware: the cache has no lane axis")
}
}
impl GpuDynKVCacheState {
pub fn reallocs(&self) -> usize {
self.reallocs
}
pub fn truncate(&mut self, len: usize) -> TractResult<()> {
if self.buffer.is_none() {
return Ok(());
}
ensure!(len <= self.len, "Can not truncate a cache of {} to {len}", self.len);
self.len = len;
Ok(())
}
}
pub fn window_output_for(
model: &TypedModel,
node: &TypedNode,
op: &GpuDynKVCache,
tolerates: impl Fn(&TypedNode) -> bool,
) -> TractResult<Option<TypedModelPatch>> {
rule_ensure!(!op.window_output);
let succs = model.all_succ(node.id)?.context("A KV cache with no consumer")?;
rule_ensure!(!succs.is_empty() && succs.iter().copied().all(tolerates));
TypedModelPatch::replace_single_op(
model,
node,
&node.inputs,
GpuDynKVCache { window_output: true, ..op.clone() },
)
.map(Some)
}
#[derive(Clone)]
pub struct GpuDynKVCache {
pub name: String,
pub past_sequence_fact: TypedFact,
pub input_sequence_fact: TypedFact,
pub axis: usize,
pub window_output: bool,
}
impl GpuDynKVCache {
pub fn from_tract_transformers(op: &DynKeyValueCache) -> Self {
Self {
name: op.name.clone(),
axis: op.axis,
past_sequence_fact: op.past_sequence_fact.clone(),
input_sequence_fact: op.input_sequence_fact.clone(),
window_output: false,
}
}
}
impl std::fmt::Debug for GpuDynKVCache {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "GpuDynKVCache({}, axis={})", self.name, self.axis)
}
}
impl PartialEq for GpuDynKVCache {
fn eq(&self, other: &Self) -> bool {
self.name == other.name
&& self.axis == other.axis
&& self.past_sequence_fact == other.past_sequence_fact
&& self.input_sequence_fact == other.input_sequence_fact
&& self.window_output == other.window_output
}
}
impl Eq for GpuDynKVCache {}
impl std::hash::Hash for GpuDynKVCache {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.name.hash(state);
self.axis.hash(state);
}
}
impl Op for GpuDynKVCache {
fn name(&self) -> StaticName {
"GpuDynKVCache".into()
}
fn info(&self) -> TractResult<Vec<String>> {
Ok(vec![format!("axis: {}, window_output: {}", self.axis, self.window_output)])
}
op_as_typed_op!();
}
impl EvalOp for GpuDynKVCache {
not_out_of_plan!();
fn state(&self, _ctx: &EvalContext) -> TractResult<Option<Box<dyn OpState>>> {
let mut state =
GpuDynKVCacheState::new(self.name.clone(), self.axis, self.past_sequence_fact.clone());
state.window_output = self.window_output;
Ok(Some(Box::new(state)))
}
}
impl TypedOp for GpuDynKVCache {
fn output_facts(&self, inputs: &[&TypedFact]) -> TractResult<TVec<TypedFact>> {
ensure!(inputs.len() == 1);
let mut facts = crate::utils::facts_to_device_facts(inputs, |facts| {
let mut fact = facts[0].without_value();
fact.shape.set(
self.axis,
self.past_sequence_fact.shape.dims()[self.axis].clone()
+ self.input_sequence_fact.shape.dims()[self.axis].clone(),
);
Ok(tvec!(fact))
})
.with_context(|| format!("Error while computing facts for {:?}", self.name()))?;
facts[0].as_device_fact_mut().unwrap().state_owned = true;
Ok(facts)
}
as_op!();
}