logicaffeine_compile/model/
ngram_hash.rs1use serde_json::Value;
13
14const MASK64: u128 = (1u128 << 64) - 1;
15const SPLITMIX_GAMMA: u64 = 0x9E37_79B9_7F4A_7C15;
16const SPLITMIX_M1: u64 = 0xBF58_476D_1CE4_E5B9;
17const SPLITMIX_M2: u64 = 0x94D0_49BB_1331_11EB;
18const PRIME_1: u64 = 10007;
19
20fn splitmix64(value: u64) -> u64 {
21 let mut v = value.wrapping_add(SPLITMIX_GAMMA);
22 v = (v ^ (v >> 30)).wrapping_mul(SPLITMIX_M1);
23 v = (v ^ (v >> 27)).wrapping_mul(SPLITMIX_M2);
24 v ^ (v >> 31)
25}
26
27fn is_prime(n: u64) -> bool {
28 if n < 2 {
29 return false;
30 }
31 if n % 2 == 0 {
32 return n == 2;
33 }
34 let mut d = 3;
35 while d * d <= n {
36 if n % d == 0 {
37 return false;
38 }
39 d += 2;
40 }
41 true
42}
43
44fn nth_prime_after(start: u64, count: u64) -> u64 {
46 let mut p = start;
47 for _ in 0..count {
48 p += 1;
49 while !is_prime(p) {
50 p += 1;
51 }
52 }
53 p
54}
55
56#[derive(Clone, Debug, PartialEq, Eq)]
58pub struct NgramHash {
59 ngram_size: usize,
60 heads_per_ngram: usize,
61 eos: u32,
62 multipliers: Vec<u64>,
63 primes: Vec<u64>,
64 offsets: Vec<u64>,
65 rows_before_padding: u64,
66 rows: u64,
67}
68
69impl NgramHash {
70 #[allow(clippy::too_many_arguments)]
72 pub fn new(
73 vocab_size: u64,
74 ngram_size: usize,
75 heads_per_ngram: usize,
76 ngram_vocab_size_base: u64,
77 divisible_by: u64,
78 ple_layer_index: u64,
79 seed: u64,
80 eos: u32,
81 ) -> NgramHash {
82 let max_long = (1u64 << 63) - 1;
83 let half_bound = (max_long / vocab_size.max(1) / 2).max(1);
84 let base_seed = seed.wrapping_add(PRIME_1.wrapping_mul(ple_layer_index));
85 let multipliers = (0..ngram_size as u64)
86 .map(|i| {
87 let value = ((base_seed as u128 + SPLITMIX_GAMMA as u128 * (i as u128 + 1)) & MASK64) as u64;
88 2 * (splitmix64(value) % half_bound) + 1
89 })
90 .collect();
91 let heads = (ngram_size - 1) * heads_per_ngram;
92 let mut primes = Vec::with_capacity(heads);
93 let mut offsets = Vec::with_capacity(heads);
94 let mut total = 0u64;
95 for h in 0..heads as u64 {
96 let global = ple_layer_index * heads as u64 + h;
97 let p = nth_prime_after(ngram_vocab_size_base - 1, global + 1);
98 primes.push(p);
99 offsets.push(total);
100 total += p;
101 }
102 let rows = total.div_ceil(divisible_by) * divisible_by;
103 NgramHash { ngram_size, heads_per_ngram, eos, multipliers, primes, offsets, rows_before_padding: total, rows }
104 }
105
106 pub fn from_config(config: &Value, ple_layer_index: u64) -> Option<NgramHash> {
109 let u = |k: &str| config.get(k).and_then(Value::as_u64);
110 let eos = match config.get("eos_token_id")? {
111 Value::Array(a) => a.first()?.as_u64()?,
112 v => v.as_u64()?,
113 };
114 Some(NgramHash::new(
115 u("vocab_size")?,
116 u("ngram_size")? as usize,
117 u("heads_per_ngram")? as usize,
118 u("ngram_vocab_size_base")?,
119 u("make_ngram_vocab_size_divisible_by")?,
120 ple_layer_index,
121 u("seed").unwrap_or(1234),
122 eos as u32,
123 ))
124 }
125
126 pub fn multipliers(&self) -> &[u64] {
127 &self.multipliers
128 }
129
130 pub fn head_primes(&self) -> &[u64] {
131 &self.primes
132 }
133
134 pub fn head_offsets(&self) -> &[u64] {
136 &self.offsets
137 }
138
139 pub fn rows_before_padding(&self) -> u64 {
140 self.rows_before_padding
141 }
142
143 pub fn rows(&self) -> u64 {
145 self.rows
146 }
147
148 pub fn ids(&self, context: &[u32], tokens: &[u32]) -> Vec<Vec<u64>> {
151 let keep = self.ngram_size - 1;
152 let mut history: Vec<u32> = if context.is_empty() { vec![self.eos; keep] } else { context.to_vec() };
153 history.extend_from_slice(tokens);
154 let mut segment_start = Vec::with_capacity(history.len());
156 let mut start = 0usize;
157 for (p, &t) in history.iter().enumerate() {
158 segment_start.push(start);
159 if t == self.eos {
160 start = p + 1;
161 }
162 }
163 let shifted = |p: usize, s: usize| -> u64 {
164 if p >= s && p - segment_start[p] >= s {
165 u64::from(history[p - s])
166 } else {
167 u64::from(self.eos)
168 }
169 };
170 let first = history.len() - tokens.len();
171 (first..history.len())
172 .map(|p| {
173 let mut row = Vec::with_capacity(self.primes.len());
174 for n in 2..=self.ngram_size {
175 let mut mixed = shifted(p, 0).wrapping_mul(self.multipliers[0]);
176 for place in 1..n {
177 mixed ^= shifted(p, place).wrapping_mul(self.multipliers[place]);
178 }
179 for h in 0..self.heads_per_ngram {
180 let head = (n - 2) * self.heads_per_ngram + h;
181 row.push(mixed % self.primes[head] + self.offsets[head]);
182 }
183 }
184 row
185 })
186 .collect()
187 }
188}