use crate::embed::protocol::{ErrorKind, WorkerError};
use ndarray::ArrayD;
use ort::session::Session;
use ort::value::Tensor;
pub const PAST_KEY_VALUES_PREFIX: &str = "past_key_values";
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct KvInput {
pub name: String,
pub kv_heads: Option<usize>,
pub head_dim: Option<usize>,
}
impl KvInput {
fn from_outlet(outlet: &ort::value::Outlet) -> Option<Self> {
let name = outlet.name();
if !name.starts_with(PAST_KEY_VALUES_PREFIX) {
return None;
}
let dims: Vec<i64> = outlet
.dtype()
.tensor_shape()
.map(|shape| shape.iter().copied().collect())
.unwrap_or_default();
let static_dim = |index: usize| {
dims.get(index)
.and_then(|dim| (*dim > 0).then_some(*dim as usize))
};
Some(Self {
name: name.to_string(),
kv_heads: static_dim(1),
head_dim: static_dim(3),
})
}
}
pub fn detect_kv_inputs(session: &Session) -> Vec<KvInput> {
session
.inputs()
.iter()
.filter_map(KvInput::from_outlet)
.collect()
}
const DEFAULT_KV_HEADS: usize = 8;
const DEFAULT_HEAD_DIM: usize = 128;
pub fn zero_length_kv_tensors(
kv_inputs: &[KvInput],
batch_size: usize,
) -> Result<Vec<(String, Tensor<f32>)>, WorkerError> {
kv_inputs
.iter()
.map(|input| {
let kv_heads = input.kv_heads.unwrap_or(DEFAULT_KV_HEADS);
let head_dim = input.head_dim.unwrap_or(DEFAULT_HEAD_DIM);
let array = ArrayD::<f32>::from_shape_vec(
ndarray::IxDyn(&[batch_size, kv_heads, 0, head_dim]),
Vec::new(),
)
.map_err(|e| WorkerError {
kind: ErrorKind::Inference,
message: format!("failed to create {} array: {}", input.name, e),
})?;
let tensor = Tensor::from_array(array).map_err(|e| WorkerError {
kind: ErrorKind::Inference,
message: format!("failed to create {} tensor: {}", input.name, e),
})?;
Ok((input.name.clone(), tensor))
})
.collect()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn kv_input_parses_declared_shape() {
let input = KvInput {
name: "past_key_values.0.key".to_string(),
kv_heads: Some(8),
head_dim: Some(128),
};
assert_eq!(input.name, "past_key_values.0.key");
assert_eq!(input.kv_heads, Some(8));
assert_eq!(input.head_dim, Some(128));
}
#[test]
fn zero_length_tensors_have_empty_sequence_dim() {
if matches!(
crate::embed::ort_discovery::discover_and_init(),
crate::embed::ort_discovery::InitResult::NotFound { .. }
) {
eprintln!("SKIP: no ONNX Runtime library available for tensor construction");
return;
}
let kv = vec![
KvInput {
name: "past_key_values.0.key".to_string(),
kv_heads: Some(8),
head_dim: Some(128),
},
KvInput {
name: "past_key_values.27.value".to_string(),
kv_heads: None,
head_dim: None,
},
];
let tensors = zero_length_kv_tensors(&kv, 2).unwrap();
assert_eq!(tensors.len(), 2);
assert_eq!(tensors[0].0, "past_key_values.0.key");
let shape: Vec<usize> = tensors[0].1.shape().iter().map(|&d| d as usize).collect();
assert_eq!(shape, vec![2, 8, 0, 128]);
let shape2: Vec<usize> = tensors[1].1.shape().iter().map(|&d| d as usize).collect();
assert_eq!(shape2, vec![2, 8, 0, 128]);
}
#[test]
fn empty_kv_list_is_noop() {
assert!(zero_length_kv_tensors(&[], 4).unwrap().is_empty());
}
}