1use serde::{Deserialize, Serialize};
4use std::{collections::BTreeMap, num::NonZeroU32};
5
6#[derive(Debug, Clone, Copy, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize, Deserialize)]
8#[serde(rename_all = "snake_case")]
9pub enum AttentionPolicy {
10 Full,
12 Sliding {
14 window: NonZeroU32,
16 },
17}
18
19impl AttentionPolicy {
20 pub fn sliding(window: u32) -> Result<Self, LayerScheduleError> {
22 let window = NonZeroU32::new(window).ok_or(LayerScheduleError::ZeroWindow)?;
23 Ok(Self::Sliding { window })
24 }
25
26 pub fn from_sliding_window(window: Option<i32>) -> Result<Self, LayerScheduleError> {
28 match window {
29 None => Ok(Self::Full),
30 Some(window) if window <= 0 => Err(LayerScheduleError::ZeroWindow),
31 Some(window) => Self::sliding(window as u32),
32 }
33 }
34
35 pub fn sliding_window_i32(self) -> Result<Option<i32>, LayerScheduleError> {
37 self.window()
38 .map(|window| {
39 i32::try_from(window.get()).map_err(|_| LayerScheduleError::WindowOutOfRange {
40 window: window.get(),
41 })
42 })
43 .transpose()
44 }
45
46 pub const fn window(self) -> Option<NonZeroU32> {
48 match self {
49 Self::Full => None,
50 Self::Sliding { window } => Some(window),
51 }
52 }
53}
54
55#[derive(Debug, Clone, Eq, Hash, PartialEq, Serialize, Deserialize)]
57#[serde(transparent)]
58pub struct LayerSchedule<P> {
59 layers: Box<[P]>,
60}
61
62impl<P> LayerSchedule<P> {
63 pub fn new(layer_count: usize, layers: Vec<P>) -> Result<Self, LayerScheduleError> {
65 if layer_count == 0 {
66 return Err(LayerScheduleError::Empty);
67 }
68 if layers.len() != layer_count {
69 return Err(LayerScheduleError::LayerCount {
70 expected: layer_count,
71 actual: layers.len(),
72 });
73 }
74 Ok(Self {
75 layers: layers.into_boxed_slice(),
76 })
77 }
78
79 pub const fn len(&self) -> usize {
81 self.layers.len()
82 }
83 pub const fn is_empty(&self) -> bool {
85 self.layers.is_empty()
86 }
87 pub fn get(&self, layer: usize) -> Option<&P> {
89 self.layers.get(layer)
90 }
91 pub fn iter(&self) -> impl ExactSizeIterator<Item = &P> + '_ {
93 self.layers.iter()
94 }
95}
96
97impl LayerSchedule<AttentionPolicy> {
98 pub fn all_full(layer_count: usize) -> Result<Self, LayerScheduleError> {
100 Self::new(layer_count, vec![AttentionPolicy::Full; layer_count])
101 }
102 pub fn all_sliding(layer_count: usize, window: u32) -> Result<Self, LayerScheduleError> {
104 Self::new(
105 layer_count,
106 vec![AttentionPolicy::sliding(window)?; layer_count],
107 )
108 }
109 pub fn from_sliding_pattern(
111 layer_count: usize,
112 pattern: &[bool],
113 window: Option<u32>,
114 ) -> Result<Self, LayerScheduleError> {
115 if pattern.len() != layer_count {
116 return Err(LayerScheduleError::LayerCount {
117 expected: layer_count,
118 actual: pattern.len(),
119 });
120 }
121 let policy = match (pattern.iter().any(|value| *value), window) {
122 (true, Some(window)) => Some(AttentionPolicy::sliding(window)?),
123 (true, None) => return Err(LayerScheduleError::MissingWindow),
124 (false, _) => None,
125 };
126 Self::new(
127 layer_count,
128 pattern
129 .iter()
130 .map(|enabled| {
131 if *enabled {
132 policy.expect("validated")
133 } else {
134 AttentionPolicy::Full
135 }
136 })
137 .collect(),
138 )
139 }
140 pub fn full_layer_count(&self) -> usize {
142 self.layers
143 .iter()
144 .filter(|p| matches!(p, AttentionPolicy::Full))
145 .count()
146 }
147 pub fn sliding_layer_count(&self) -> usize {
149 self.len() - self.full_layer_count()
150 }
151 pub fn sliding_windows(&self) -> BTreeMap<NonZeroU32, usize> {
153 let mut result = BTreeMap::new();
154 for window in self.iter().copied().filter_map(AttentionPolicy::window) {
155 *result.entry(window).or_default() += 1;
156 }
157 result
158 }
159 pub fn fingerprint_component(&self) -> String {
161 self.iter()
162 .map(|policy| match policy {
163 AttentionPolicy::Full => "f".into(),
164 AttentionPolicy::Sliding { window } => format!("s{}", window.get()),
165 })
166 .collect::<Vec<_>>()
167 .join(",")
168 }
169}
170
171#[derive(Debug, Clone, Eq, PartialEq, thiserror::Error)]
173pub enum LayerScheduleError {
174 #[error("layer schedule must contain at least one layer")]
176 Empty,
177 #[error("layer schedule has {actual} entries for {expected} decoder layers")]
179 LayerCount {
180 expected: usize,
182 actual: usize,
184 },
185 #[error("sliding attention window must be positive")]
187 ZeroWindow,
188 #[error("sliding attention is enabled for at least one layer without a window")]
190 MissingWindow,
191 #[error("sliding attention window {window} exceeds i32")]
193 WindowOutOfRange {
194 window: u32,
196 },
197}
198
199#[cfg(test)]
200mod tests {
201 use super::*;
202 #[test]
203 fn validates_attention_schedule() {
204 let schedule = LayerSchedule::new(
205 3,
206 vec![
207 AttentionPolicy::Full,
208 AttentionPolicy::sliding(8).unwrap(),
209 AttentionPolicy::Full,
210 ],
211 )
212 .unwrap();
213 assert_eq!(schedule.fingerprint_component(), "f,s8,f");
214 assert_eq!(schedule.sliding_layer_count(), 1);
215 assert!(LayerSchedule::from_sliding_pattern(2, &[true], Some(4)).is_err());
216 }
217}