1use infernum_core::SamplingParams;
4
5pub struct Sampler {
7 params: SamplingParams,
8 rng: Option<fastrand::Rng>,
9}
10
11impl Sampler {
12 #[must_use]
14 pub fn new(params: SamplingParams) -> Self {
15 let rng = params.seed.map(|seed| {
16 let mut rng = fastrand::Rng::new();
17 rng.seed(seed);
18 rng
19 });
20
21 Self { params, rng }
22 }
23
24 #[must_use]
26 pub fn sample(&mut self, logits: &[f32]) -> u32 {
27 if self.params.temperature == 0.0 {
28 return self.argmax(logits);
30 }
31
32 let scaled: Vec<f32> = logits
34 .iter()
35 .map(|&l| l / self.params.temperature)
36 .collect();
37
38 let filtered = if self.params.top_k > 0 {
40 self.top_k_filter(&scaled, self.params.top_k as usize)
41 } else {
42 scaled
43 };
44
45 let filtered = if self.params.top_p < 1.0 {
47 self.top_p_filter(&filtered, self.params.top_p)
48 } else {
49 filtered
50 };
51
52 let filtered = if self.params.min_p > 0.0 {
54 self.min_p_filter(&filtered, self.params.min_p)
55 } else {
56 filtered
57 };
58
59 self.categorical_sample(&filtered)
61 }
62
63 fn argmax(&self, logits: &[f32]) -> u32 {
65 logits
66 .iter()
67 .enumerate()
68 .max_by(|(_, a), (_, b)| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal))
69 .map(|(i, _)| i as u32)
70 .unwrap_or(0)
71 }
72
73 fn top_k_filter(&self, logits: &[f32], k: usize) -> Vec<f32> {
75 let mut indexed: Vec<(usize, f32)> = logits.iter().copied().enumerate().collect();
76 indexed.sort_by(|(_, a), (_, b)| b.partial_cmp(a).unwrap_or(std::cmp::Ordering::Equal));
77
78 let mut result = vec![f32::NEG_INFINITY; logits.len()];
79 for (i, v) in indexed.into_iter().take(k) {
80 result[i] = v;
81 }
82 result
83 }
84
85 fn top_p_filter(&self, logits: &[f32], p: f32) -> Vec<f32> {
87 let probs = self.softmax(logits);
88 let mut indexed: Vec<(usize, f32)> = probs.iter().copied().enumerate().collect();
89 indexed.sort_by(|(_, a), (_, b)| b.partial_cmp(a).unwrap_or(std::cmp::Ordering::Equal));
90
91 let mut cumsum = 0.0;
92 let mut result = vec![f32::NEG_INFINITY; logits.len()];
93
94 for (i, prob) in indexed {
95 if cumsum < p {
96 result[i] = logits[i];
97 cumsum += prob;
98 }
99 }
100
101 result
102 }
103
104 fn min_p_filter(&self, logits: &[f32], min_p: f32) -> Vec<f32> {
106 let probs = self.softmax(logits);
107 let max_prob = probs.iter().cloned().fold(f32::NEG_INFINITY, f32::max);
108 let threshold = max_prob * min_p;
109
110 logits
111 .iter()
112 .zip(probs.iter())
113 .map(|(&l, &p)| if p >= threshold { l } else { f32::NEG_INFINITY })
114 .collect()
115 }
116
117 fn softmax(&self, logits: &[f32]) -> Vec<f32> {
119 let max = logits.iter().cloned().fold(f32::NEG_INFINITY, f32::max);
120 let exp: Vec<f32> = logits.iter().map(|&l| (l - max).exp()).collect();
121 let sum: f32 = exp.iter().sum();
122 exp.iter().map(|&e| e / sum).collect()
123 }
124
125 fn categorical_sample(&mut self, logits: &[f32]) -> u32 {
127 let probs = self.softmax(logits);
128 let r = self.random_f32();
129
130 let mut cumsum = 0.0;
131 for (i, &p) in probs.iter().enumerate() {
132 cumsum += p;
133 if r < cumsum {
134 return i as u32;
135 }
136 }
137
138 (probs.len() - 1) as u32
139 }
140
141 fn random_f32(&mut self) -> f32 {
143 if let Some(rng) = &mut self.rng {
144 rng.f32()
145 } else {
146 fastrand::f32()
147 }
148 }
149
150 #[must_use]
152 pub fn params(&self) -> &SamplingParams {
153 &self.params
154 }
155
156 #[must_use]
158 pub fn is_stop_token(&self, text: &str) -> bool {
159 self.params
160 .stop_sequences
161 .iter()
162 .any(|stop| text.contains(stop))
163 }
164}
165
166#[cfg(test)]
167mod tests {
168 use super::*;
169
170 #[test]
171 fn test_greedy_sampling() {
172 let params = SamplingParams::greedy();
173 let mut sampler = Sampler::new(params);
174
175 let logits = vec![1.0, 5.0, 2.0, 0.5];
176 assert_eq!(sampler.sample(&logits), 1);
177 }
178
179 #[test]
180 fn test_deterministic_with_seed() {
181 let params = SamplingParams::balanced().with_seed(42);
182 let mut sampler1 = Sampler::new(params.clone());
183 let mut sampler2 = Sampler::new(params);
184
185 let logits = vec![1.0, 1.0, 1.0, 1.0];
186 assert_eq!(sampler1.sample(&logits), sampler2.sample(&logits));
187 }
188}