use serde::{Deserialize, Serialize};
use std::{collections::BTreeMap, num::NonZeroU32};
#[derive(Debug, Clone, Copy, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum AttentionPolicy {
Full,
Sliding {
window: NonZeroU32,
},
}
impl AttentionPolicy {
pub fn sliding(window: u32) -> Result<Self, LayerScheduleError> {
let window = NonZeroU32::new(window).ok_or(LayerScheduleError::ZeroWindow)?;
Ok(Self::Sliding { window })
}
pub fn from_sliding_window(window: Option<i32>) -> Result<Self, LayerScheduleError> {
match window {
None => Ok(Self::Full),
Some(window) if window <= 0 => Err(LayerScheduleError::ZeroWindow),
Some(window) => Self::sliding(window as u32),
}
}
pub fn sliding_window_i32(self) -> Result<Option<i32>, LayerScheduleError> {
self.window()
.map(|window| {
i32::try_from(window.get()).map_err(|_| LayerScheduleError::WindowOutOfRange {
window: window.get(),
})
})
.transpose()
}
pub const fn window(self) -> Option<NonZeroU32> {
match self {
Self::Full => None,
Self::Sliding { window } => Some(window),
}
}
}
#[derive(Debug, Clone, Eq, Hash, PartialEq, Serialize, Deserialize)]
#[serde(transparent)]
pub struct LayerSchedule<P> {
layers: Box<[P]>,
}
impl<P> LayerSchedule<P> {
pub fn new(layer_count: usize, layers: Vec<P>) -> Result<Self, LayerScheduleError> {
if layer_count == 0 {
return Err(LayerScheduleError::Empty);
}
if layers.len() != layer_count {
return Err(LayerScheduleError::LayerCount {
expected: layer_count,
actual: layers.len(),
});
}
Ok(Self {
layers: layers.into_boxed_slice(),
})
}
pub const fn len(&self) -> usize {
self.layers.len()
}
pub const fn is_empty(&self) -> bool {
self.layers.is_empty()
}
pub fn get(&self, layer: usize) -> Option<&P> {
self.layers.get(layer)
}
pub fn iter(&self) -> impl ExactSizeIterator<Item = &P> + '_ {
self.layers.iter()
}
}
impl LayerSchedule<AttentionPolicy> {
pub fn all_full(layer_count: usize) -> Result<Self, LayerScheduleError> {
Self::new(layer_count, vec![AttentionPolicy::Full; layer_count])
}
pub fn all_sliding(layer_count: usize, window: u32) -> Result<Self, LayerScheduleError> {
Self::new(
layer_count,
vec![AttentionPolicy::sliding(window)?; layer_count],
)
}
pub fn from_sliding_pattern(
layer_count: usize,
pattern: &[bool],
window: Option<u32>,
) -> Result<Self, LayerScheduleError> {
if pattern.len() != layer_count {
return Err(LayerScheduleError::LayerCount {
expected: layer_count,
actual: pattern.len(),
});
}
let policy = match (pattern.iter().any(|value| *value), window) {
(true, Some(window)) => Some(AttentionPolicy::sliding(window)?),
(true, None) => return Err(LayerScheduleError::MissingWindow),
(false, _) => None,
};
Self::new(
layer_count,
pattern
.iter()
.map(|enabled| {
if *enabled {
policy.expect("validated")
} else {
AttentionPolicy::Full
}
})
.collect(),
)
}
pub fn full_layer_count(&self) -> usize {
self.layers
.iter()
.filter(|p| matches!(p, AttentionPolicy::Full))
.count()
}
pub fn sliding_layer_count(&self) -> usize {
self.len() - self.full_layer_count()
}
pub fn sliding_windows(&self) -> BTreeMap<NonZeroU32, usize> {
let mut result = BTreeMap::new();
for window in self.iter().copied().filter_map(AttentionPolicy::window) {
*result.entry(window).or_default() += 1;
}
result
}
pub fn fingerprint_component(&self) -> String {
self.iter()
.map(|policy| match policy {
AttentionPolicy::Full => "f".into(),
AttentionPolicy::Sliding { window } => format!("s{}", window.get()),
})
.collect::<Vec<_>>()
.join(",")
}
}
#[derive(Debug, Clone, Eq, PartialEq, thiserror::Error)]
pub enum LayerScheduleError {
#[error("layer schedule must contain at least one layer")]
Empty,
#[error("layer schedule has {actual} entries for {expected} decoder layers")]
LayerCount {
expected: usize,
actual: usize,
},
#[error("sliding attention window must be positive")]
ZeroWindow,
#[error("sliding attention is enabled for at least one layer without a window")]
MissingWindow,
#[error("sliding attention window {window} exceeds i32")]
WindowOutOfRange {
window: u32,
},
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn validates_attention_schedule() {
let schedule = LayerSchedule::new(
3,
vec![
AttentionPolicy::Full,
AttentionPolicy::sliding(8).unwrap(),
AttentionPolicy::Full,
],
)
.unwrap();
assert_eq!(schedule.fingerprint_component(), "f,s8,f");
assert_eq!(schedule.sliding_layer_count(), 1);
assert!(LayerSchedule::from_sliding_pattern(2, &[true], Some(4)).is_err());
}
}