optirs_learned/transformer/architecture/
positional_encoding.rs1use std::fmt::Debug;
2use scirs2_core::ndarray::{s, Array1, Array2};
8use scirs2_core::numeric::Float;
9
10use super::super::TransformerOptimizerConfig;
11use crate::error::{OptimError, Result};
12
13#[derive(Debug, Clone, Copy, PartialEq, Eq)]
15pub enum PositionalEncodingType {
16 Sinusoidal,
18 Learned,
20 Rotary,
22 Relative,
24 ALiBi,
26}
27
28#[derive(Debug, Clone)]
30pub struct PositionalEncoder<T: Float + Debug + Send + Sync + 'static> {
31 encoding_type: PositionalEncodingType,
33
34 cached_encodings: Option<Array2<T>>,
36
37 max_seqlen: usize,
39
40 modeldim: usize,
42
43 position_embeddings: Option<Array2<T>>,
45
46 alibi_slopes: Option<Array1<T>>,
48}
49
50impl<T: Float + Debug + Default + Clone + Send + Sync + 'static> PositionalEncoder<T> {
51 pub fn new(config: &TransformerOptimizerConfig) -> Result<Self> {
53 let max_seqlen = config.max_sequence_length;
54 let modeldim = config.modeldim;
55
56 let mut cached_encodings = None;
57 let mut position_embeddings = None;
58 let mut alibi_slopes = None;
59
60 match config.pos_encoding_type {
61 PositionalEncodingType::Sinusoidal => {
62 cached_encodings = Some(Self::sinusoidal_table(max_seqlen, modeldim));
63 }
64 PositionalEncodingType::Learned => {
65 let mut rng = scirs2_core::random::thread_rng();
67 let mut embeddings = Array2::zeros((max_seqlen, modeldim));
68
69 let bound = (6.0 / (max_seqlen + modeldim) as f64).sqrt();
71 for elem in embeddings.iter_mut() {
72 *elem = scirs2_core::numeric::NumCast::from(
73 (rng.random::<f64>() - 0.5) * 2.0 * bound,
74 )
75 .unwrap_or_else(|| T::zero());
76 }
77 position_embeddings = Some(embeddings);
78 }
79 PositionalEncodingType::ALiBi => {
80 let numheads = config.numheads.max(1);
82 let mut slopes = Array1::zeros(numheads);
83
84 for h in 0..numheads {
85 slopes[h] = scirs2_core::numeric::NumCast::from(
86 2.0_f64.powf(-8.0 * (h + 1) as f64 / numheads as f64),
87 )
88 .unwrap_or_else(|| T::zero());
89 }
90 alibi_slopes = Some(slopes);
91 }
92 _ => {
93 cached_encodings = Some(Self::sinusoidal_table(max_seqlen, modeldim));
97 }
98 }
99
100 Ok(Self {
101 encoding_type: config.pos_encoding_type,
102 cached_encodings,
103 max_seqlen,
104 modeldim,
105 position_embeddings,
106 alibi_slopes,
107 })
108 }
109
110 fn inverse_frequency(i: usize, modeldim: usize) -> f64 {
119 let exponent = 2.0 * ((i / 2) as f64) / modeldim.max(1) as f64;
120 1.0 / 10000.0_f64.powf(exponent)
121 }
122
123 fn sinusoidal_table(max_seqlen: usize, modeldim: usize) -> Array2<T> {
125 let mut encodings = Array2::zeros((max_seqlen, modeldim));
126
127 for pos in 0..max_seqlen {
128 for i in 0..modeldim {
129 let angle = pos as f64 * Self::inverse_frequency(i, modeldim);
130 let value = if i % 2 == 0 { angle.sin() } else { angle.cos() };
131 encodings[[pos, i]] =
132 scirs2_core::numeric::NumCast::from(value).unwrap_or_else(|| T::zero());
133 }
134 }
135
136 encodings
137 }
138
139 pub fn encode(&self, input: &Array2<T>) -> Result<Array2<T>> {
141 let (seq_len, modeldim) = input.dim();
142
143 if seq_len > self.max_seqlen {
144 return Err(OptimError::InvalidConfig(format!(
145 "Sequence length {} exceeds maximum {}",
146 seq_len, self.max_seqlen
147 )));
148 }
149
150 if modeldim != self.modeldim {
151 return Err(OptimError::InvalidConfig(format!(
152 "Model dimension {} doesn't match expected {}",
153 modeldim, self.modeldim
154 )));
155 }
156
157 let mut output = input.clone();
158
159 match self.encoding_type {
160 PositionalEncodingType::Sinusoidal => {
161 if let Some(ref encodings) = self.cached_encodings {
162 let pos_enc = encodings.slice(s![..seq_len, ..]);
163 output = output + pos_enc;
164 }
165 }
166 PositionalEncodingType::Learned => {
167 if let Some(ref embeddings) = self.position_embeddings {
168 let pos_emb = embeddings.slice(s![..seq_len, ..]);
169 output = output + pos_emb;
170 }
171 }
172 PositionalEncodingType::Rotary => {
173 }
177 PositionalEncodingType::Relative => {
178 }
182 PositionalEncodingType::ALiBi => {
183 }
186 }
187
188 Ok(output)
189 }
190
191 pub fn get_alibi_slopes(&self) -> Option<&Array1<T>> {
193 self.alibi_slopes.as_ref()
194 }
195
196 pub fn encoding_type(&self) -> PositionalEncodingType {
198 self.encoding_type
199 }
200
201 pub fn max_sequence_length(&self) -> usize {
203 self.max_seqlen
204 }
205
206 pub fn model_dimension(&self) -> usize {
208 self.modeldim
209 }
210
211 pub fn update_embeddings(&mut self, new_embeddings: Array2<T>) -> Result<()> {
213 match self.encoding_type {
214 PositionalEncodingType::Learned => {
215 let (pos_len, model_dim) = new_embeddings.dim();
216 if pos_len != self.max_seqlen || model_dim != self.modeldim {
217 return Err(OptimError::InvalidConfig(
218 "New embeddings dimensions don't match encoder configuration".to_string(),
219 ));
220 }
221 self.position_embeddings = Some(new_embeddings);
222 Ok(())
223 }
224 _ => Err(OptimError::InvalidConfig(
225 "Position embeddings can only be updated for learned encoding type".to_string(),
226 )),
227 }
228 }
229
230 pub fn compute_sinusoidal_position(&self, position: usize) -> Result<Array1<T>> {
232 if position >= self.max_seqlen {
233 return Err(OptimError::InvalidConfig(
234 "Position exceeds maximum sequence length".to_string(),
235 ));
236 }
237
238 let mut encoding = Array1::zeros(self.modeldim);
239 for i in 0..self.modeldim {
240 let angle = position as f64 * Self::inverse_frequency(i, self.modeldim);
241 let value = if i % 2 == 0 { angle.sin() } else { angle.cos() };
242 encoding[i] = scirs2_core::numeric::NumCast::from(value).unwrap_or_else(|| T::zero());
243 }
244
245 Ok(encoding)
246 }
247
248 pub fn apply_alibi_bias(
250 &self,
251 attention_scores: &mut Array2<T>,
252 head_idx: usize,
253 ) -> Result<()> {
254 if self.encoding_type != PositionalEncodingType::ALiBi {
255 return Ok(()); }
257
258 if let Some(ref slopes) = self.alibi_slopes {
259 if head_idx >= slopes.len() {
260 return Err(OptimError::InvalidConfig(
261 "Head index exceeds number of ALiBi slopes".to_string(),
262 ));
263 }
264
265 let slope = slopes[head_idx];
266 let (rows, cols) = attention_scores.dim();
267
268 for i in 0..rows {
269 for j in 0..cols {
270 let distance: T =
271 scirs2_core::numeric::NumCast::from((i as i64 - j as i64).abs() as f64)
272 .unwrap_or_else(|| T::zero());
273 attention_scores[[i, j]] = attention_scores[[i, j]] - slope * distance;
274 }
275 }
276 }
277
278 Ok(())
279 }
280}
281
282#[cfg(test)]
283mod tests {
284 use super::*;
285 use crate::transformer::TransformerOptimizerConfig;
286
287 fn config(encoding: PositionalEncodingType) -> TransformerOptimizerConfig {
288 TransformerOptimizerConfig {
289 modeldim: 8,
290 numheads: 2,
291 max_sequence_length: 16,
292 pos_encoding_type: encoding,
293 ..Default::default()
294 }
295 }
296
297 #[test]
298 fn sinusoidal_pairs_share_a_frequency() {
299 let encoder = PositionalEncoder::<f64>::new(&config(PositionalEncodingType::Sinusoidal))
300 .expect("encoder creation");
301 let encoding = encoder
302 .compute_sinusoidal_position(3)
303 .expect("position encoding");
304
305 let omega = 1.0 / 10000.0_f64.powf(2.0 * 1.0 / 8.0);
307 assert!((encoding[2] - (3.0 * omega).sin()).abs() < 1e-12);
308 assert!((encoding[3] - (3.0 * omega).cos()).abs() < 1e-12);
309
310 assert!((encoding[2] * encoding[2] + encoding[3] * encoding[3] - 1.0).abs() < 1e-12);
312 }
313
314 #[test]
315 fn position_zero_is_alternating_zero_one() {
316 let encoder = PositionalEncoder::<f64>::new(&config(PositionalEncodingType::Sinusoidal))
317 .expect("encoder creation");
318 let encoding = encoder
319 .compute_sinusoidal_position(0)
320 .expect("position encoding");
321 for i in 0..8 {
322 let expected = if i % 2 == 0 { 0.0 } else { 1.0 };
323 assert!((encoding[i] - expected).abs() < 1e-12, "dim {i}");
324 }
325 }
326
327 #[test]
328 fn encode_adds_the_cached_table() {
329 let encoder = PositionalEncoder::<f64>::new(&config(PositionalEncodingType::Sinusoidal))
330 .expect("encoder creation");
331 let input = Array2::<f64>::zeros((4, 8));
332 let encoded = encoder.encode(&input).expect("encode");
333 let reference = encoder
334 .compute_sinusoidal_position(2)
335 .expect("position encoding");
336 for i in 0..8 {
337 assert!((encoded[[2, i]] - reference[i]).abs() < 1e-12);
338 }
339 }
340
341 #[test]
342 fn alibi_bias_penalizes_distance() {
343 let encoder = PositionalEncoder::<f64>::new(&config(PositionalEncodingType::ALiBi))
344 .expect("encoder creation");
345 let mut scores = Array2::<f64>::zeros((4, 4));
346 encoder
347 .apply_alibi_bias(&mut scores, 0)
348 .expect("alibi bias");
349 assert_eq!(scores[[0, 0]], 0.0);
350 assert!(
351 scores[[0, 3]] < scores[[0, 1]],
352 "farther must be penalized more"
353 );
354 }
355
356 #[test]
357 fn oversized_sequences_are_rejected() {
358 let encoder = PositionalEncoder::<f64>::new(&config(PositionalEncodingType::Sinusoidal))
359 .expect("encoder creation");
360 let input = Array2::<f64>::zeros((64, 8));
361 assert!(encoder.encode(&input).is_err());
362 }
363}