1use ferrum_types::{Result, SamplingParams, TokenId};
8use rand::{RngCore, SeedableRng};
9use rand_chacha::ChaCha12Rng;
10use serde::{Deserialize, Serialize};
11use std::collections::HashMap;
12use std::fmt;
13
14pub const SAMPLING_RNG_ALGORITHM_ID: &str = "chacha12-rand-core-pcg32-u64-v1";
20
21#[derive(Clone, Debug)]
22pub struct SamplingRng {
23 inner: ChaCha12Rng,
24}
25
26impl SamplingRng {
27 pub fn seeded(seed: u64) -> Self {
28 Self {
29 inner: ChaCha12Rng::seed_from_u64(seed),
30 }
31 }
32
33 pub fn from_seed_bytes(seed: [u8; 32]) -> Self {
34 Self {
35 inner: ChaCha12Rng::from_seed(seed),
36 }
37 }
38
39 pub fn from_entropy() -> Self {
40 let mut entropy = rand::rng();
41 Self {
42 inner: ChaCha12Rng::from_rng(&mut entropy),
43 }
44 }
45
46 pub const fn algorithm_id() -> &'static str {
47 SAMPLING_RNG_ALGORITHM_ID
48 }
49}
50
51impl RngCore for SamplingRng {
52 fn next_u32(&mut self) -> u32 {
53 self.inner.next_u32()
54 }
55
56 fn next_u64(&mut self) -> u64 {
57 self.inner.next_u64()
58 }
59
60 fn fill_bytes(&mut self, dest: &mut [u8]) {
61 self.inner.fill_bytes(dest);
62 }
63}
64
65#[derive(Debug)]
67pub struct SamplingContext<'a> {
68 pub step: usize,
70 pub sampling_params: &'a SamplingParams,
72 pub logits: &'a mut [f32],
74 pub previous_tokens: &'a [TokenId],
76 pub token_frequencies: &'a HashMap<TokenId, usize>,
78 pub vocab_size: usize,
80 pub metadata: HashMap<String, f32>,
82}
83
84impl<'a> SamplingContext<'a> {
85 pub fn new(
87 step: usize,
88 sampling_params: &'a SamplingParams,
89 logits: &'a mut [f32],
90 previous_tokens: &'a [TokenId],
91 token_frequencies: &'a HashMap<TokenId, usize>,
92 vocab_size: usize,
93 ) -> Self {
94 Self {
95 step,
96 sampling_params,
97 logits,
98 previous_tokens,
99 token_frequencies,
100 vocab_size,
101 metadata: HashMap::new(),
102 }
103 }
104
105 pub fn get_logit(&self, token_id: TokenId) -> Option<f32> {
107 if usize::from(token_id) < self.logits.len() {
108 Some(self.logits[usize::from(token_id)])
109 } else {
110 None
111 }
112 }
113
114 pub fn set_logit(&mut self, token_id: TokenId, value: f32) -> bool {
116 if usize::from(token_id) < self.logits.len() {
117 self.logits[usize::from(token_id)] = value;
118 true
119 } else {
120 false
121 }
122 }
123
124 pub fn mask_tokens(&mut self, token_ids: &[TokenId]) {
126 for &token_id in token_ids {
127 if usize::from(token_id) < self.logits.len() {
128 self.logits[usize::from(token_id)] = f32::NEG_INFINITY;
129 }
130 }
131 }
132}
133
134pub trait LogitsProcessor: Send + Sync {
136 fn process(&self, ctx: &mut SamplingContext) -> Result<()>;
138
139 fn name(&self) -> &str;
141
142 fn priority(&self) -> ProcessorPriority {
144 ProcessorPriority::Normal
145 }
146}
147
148#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
150pub enum ProcessorPriority {
151 High = 3,
153 Normal = 2,
155 Low = 1,
157}
158
159pub trait Sampler: Send + Sync {
161 fn sample(&self, logits: &[f32], rng: &mut dyn RngCore) -> Result<TokenId>;
163
164 fn sample_with_context(&self, ctx: &SamplingContext, rng: &mut dyn RngCore) -> Result<TokenId> {
166 self.sample(ctx.logits, rng)
167 }
168
169 fn name(&self) -> &str;
171
172 fn is_deterministic(&self) -> bool;
174}
175
176pub trait MultiSampler: Sampler {
178 fn sample_multiple(
180 &self,
181 logits: &[f32],
182 num_samples: usize,
183 rng: &mut dyn RngCore,
184 ) -> Result<Vec<TokenId>>;
185
186 fn sample_with_probabilities(
188 &self,
189 logits: &[f32],
190 rng: &mut dyn RngCore,
191 ) -> Result<(TokenId, Vec<f32>)>;
192}
193
194pub struct LogitsProcessorChain {
196 processors: Vec<Box<dyn LogitsProcessor>>,
197}
198
199impl LogitsProcessorChain {
200 pub fn new() -> Self {
202 Self {
203 processors: Vec::new(),
204 }
205 }
206
207 pub fn add_processor(mut self, processor: Box<dyn LogitsProcessor>) -> Self {
209 self.processors.push(processor);
210 self.processors
212 .sort_by(|a, b| b.priority().cmp(&a.priority()));
213 self
214 }
215
216 pub fn process(&self, ctx: &mut SamplingContext) -> Result<()> {
218 for processor in &self.processors {
219 processor.process(ctx)?;
220 }
221 Ok(())
222 }
223
224 pub fn processor_names(&self) -> Vec<&str> {
226 self.processors.iter().map(|p| p.name()).collect()
227 }
228
229 pub fn is_empty(&self) -> bool {
231 self.processors.is_empty()
232 }
233}
234
235impl fmt::Debug for LogitsProcessorChain {
236 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
237 f.debug_list()
238 .entries(self.processors.iter().map(|processor| processor.name()))
239 .finish()
240 }
241}
242
243impl Default for LogitsProcessorChain {
244 fn default() -> Self {
245 Self::new()
246 }
247}
248
249pub struct TemperatureProcessor {
253 pub temperature: f32,
254}
255
256impl TemperatureProcessor {
257 pub fn new(temperature: f32) -> Self {
258 Self { temperature }
259 }
260}
261
262impl LogitsProcessor for TemperatureProcessor {
263 fn process(&self, ctx: &mut SamplingContext) -> Result<()> {
264 if self.temperature > 0.0 && self.temperature != 1.0 {
265 for logit in ctx.logits.iter_mut() {
266 *logit /= self.temperature;
267 }
268 }
269 Ok(())
270 }
271
272 fn name(&self) -> &str {
273 "temperature"
274 }
275
276 fn priority(&self) -> ProcessorPriority {
277 ProcessorPriority::Normal
280 }
281}
282
283pub struct TopKProcessor {
285 pub k: usize,
286}
287
288impl TopKProcessor {
289 pub fn new(k: usize) -> Self {
290 Self { k }
291 }
292}
293
294impl LogitsProcessor for TopKProcessor {
295 fn process(&self, ctx: &mut SamplingContext) -> Result<()> {
296 if self.k > 0 && self.k < ctx.logits.len() {
297 let threshold = if ctx.logits.iter().any(|logit| logit.is_nan()) {
298 let mut indices: Vec<usize> = (0..ctx.logits.len()).collect();
302 indices.sort_by(|&a, &b| {
303 ctx.logits[b]
304 .partial_cmp(&ctx.logits[a])
305 .unwrap_or(std::cmp::Ordering::Equal)
306 });
307 ctx.logits[indices[self.k - 1]]
308 } else {
309 let mut candidates = ctx.logits.to_vec();
312 let (_, threshold, _) = candidates.select_nth_unstable_by(self.k - 1, |a, b| {
313 b.partial_cmp(a).unwrap_or(std::cmp::Ordering::Equal)
314 });
315 *threshold
316 };
317
318 for logit in ctx.logits.iter_mut() {
320 if *logit < threshold {
321 *logit = f32::NEG_INFINITY;
322 }
323 }
324 }
325 Ok(())
326 }
327
328 fn name(&self) -> &str {
329 "top_k"
330 }
331
332 fn priority(&self) -> ProcessorPriority {
333 ProcessorPriority::Low
334 }
335}
336
337pub struct TopPProcessor {
339 pub p: f32,
340}
341
342impl TopPProcessor {
343 pub fn new(p: f32) -> Self {
344 Self { p }
345 }
346}
347
348impl LogitsProcessor for TopPProcessor {
349 fn process(&self, ctx: &mut SamplingContext) -> Result<()> {
350 if self.p < 1.0 && self.p > 0.0 {
351 let mut candidates = ctx
355 .logits
356 .iter()
357 .copied()
358 .enumerate()
359 .filter(|(_, logit)| logit.is_finite())
360 .collect::<Vec<_>>();
361 if candidates.is_empty() {
362 return Ok(());
363 }
364
365 candidates.sort_by(|(left_idx, left), (right_idx, right)| {
366 right.total_cmp(left).then_with(|| left_idx.cmp(right_idx))
367 });
368
369 let max_logit = candidates[0].1;
370 let sum = candidates
371 .iter()
372 .map(|(_, logit)| (*logit - max_logit).exp())
373 .sum::<f32>();
374 let mut cum_prob = 0.0;
375 let mut cutoff_idx = candidates.len();
376 for (i, (_, logit)) in candidates.iter().enumerate() {
377 cum_prob += (*logit - max_logit).exp() / sum;
378 if cum_prob >= self.p {
379 cutoff_idx = i + 1;
380 break;
381 }
382 }
383
384 for (idx, _) in candidates.into_iter().skip(cutoff_idx) {
385 ctx.logits[idx] = f32::NEG_INFINITY;
386 }
387 }
388 Ok(())
389 }
390
391 fn name(&self) -> &str {
392 "top_p"
393 }
394
395 fn priority(&self) -> ProcessorPriority {
396 ProcessorPriority::Low
397 }
398}
399
400pub struct MinPProcessor {
406 pub min_p: f32,
407}
408
409impl MinPProcessor {
410 pub fn new(min_p: f32) -> Self {
411 Self { min_p }
412 }
413}
414
415impl LogitsProcessor for MinPProcessor {
416 fn process(&self, ctx: &mut SamplingContext) -> Result<()> {
417 if self.min_p > 0.0 && self.min_p <= 1.0 {
418 let max_logit = ctx.logits.iter().copied().fold(f32::NEG_INFINITY, f32::max);
419 let threshold = max_logit + self.min_p.ln();
420 for logit in ctx.logits.iter_mut() {
421 if *logit < threshold {
422 *logit = f32::NEG_INFINITY;
423 }
424 }
425 }
426 Ok(())
427 }
428
429 fn name(&self) -> &str {
430 "min_p"
431 }
432
433 fn priority(&self) -> ProcessorPriority {
434 ProcessorPriority::Low
435 }
436}
437
438pub struct RepetitionPenaltyProcessor {
440 pub penalty: f32,
441}
442
443impl RepetitionPenaltyProcessor {
444 pub fn new(penalty: f32) -> Self {
445 Self { penalty }
446 }
447}
448
449impl LogitsProcessor for RepetitionPenaltyProcessor {
450 fn process(&self, ctx: &mut SamplingContext) -> Result<()> {
451 if self.penalty != 1.0 {
452 for &token_id in ctx.token_frequencies.keys() {
453 if usize::from(token_id) >= ctx.logits.len() {
454 continue;
455 }
456 let idx = usize::from(token_id);
457 let current_logit = ctx.logits[idx];
458 if current_logit > 0.0 {
459 ctx.logits[idx] = current_logit / self.penalty;
460 } else {
461 ctx.logits[idx] = current_logit * self.penalty;
462 }
463 }
464 }
465 Ok(())
466 }
467
468 fn name(&self) -> &str {
469 "repetition_penalty"
470 }
471
472 fn priority(&self) -> ProcessorPriority {
473 ProcessorPriority::High }
475}
476
477pub struct PresenceFrequencyPenaltyProcessor {
483 pub presence_penalty: f32,
484 pub frequency_penalty: f32,
485}
486
487impl PresenceFrequencyPenaltyProcessor {
488 pub fn new(presence_penalty: f32, frequency_penalty: f32) -> Self {
489 Self {
490 presence_penalty,
491 frequency_penalty,
492 }
493 }
494}
495
496impl LogitsProcessor for PresenceFrequencyPenaltyProcessor {
497 fn process(&self, ctx: &mut SamplingContext) -> Result<()> {
498 if self.presence_penalty == 0.0 && self.frequency_penalty == 0.0 {
499 return Ok(());
500 }
501 for (&token_id, &count) in ctx.token_frequencies {
502 let idx = usize::from(token_id);
503 if idx >= ctx.logits.len() || count == 0 {
504 continue;
505 }
506 ctx.logits[idx] -= self.presence_penalty + self.frequency_penalty * count as f32;
507 }
508 Ok(())
509 }
510
511 fn name(&self) -> &str {
512 "presence_frequency_penalty"
513 }
514
515 fn priority(&self) -> ProcessorPriority {
516 ProcessorPriority::High
517 }
518}
519
520pub struct GreedySampler;
524
525impl Sampler for GreedySampler {
526 fn sample(&self, logits: &[f32], _rng: &mut dyn RngCore) -> Result<TokenId> {
527 let max_idx = logits
528 .iter()
529 .enumerate()
530 .filter(|(_, logit)| logit.is_finite())
531 .reduce(|best, candidate| match candidate.1.total_cmp(best.1) {
532 std::cmp::Ordering::Greater => candidate,
533 std::cmp::Ordering::Equal if candidate.0 < best.0 => candidate,
534 _ => best,
535 })
536 .map(|(idx, _)| idx)
537 .ok_or_else(|| {
538 ferrum_types::FerrumError::backend("No finite logits available for sampling")
539 })?;
540
541 Ok(TokenId::new(max_idx as u32))
542 }
543
544 fn name(&self) -> &str {
545 "greedy"
546 }
547
548 fn is_deterministic(&self) -> bool {
549 true
550 }
551}
552
553#[cfg(test)]
554mod greedy_sampler_tests {
555 use super::{GreedySampler, Sampler, SamplingRng};
556
557 #[test]
558 fn ties_choose_the_lowest_token_id() {
559 let mut rng = SamplingRng::seeded(1);
560 let token = GreedySampler
561 .sample(&[-1.0, 4.0, 4.0, f32::NAN], &mut rng)
562 .unwrap();
563 assert_eq!(token.get(), 1);
564 }
565
566 #[test]
567 fn non_finite_logits_are_never_selected() {
568 let mut rng = SamplingRng::seeded(1);
569 let token = GreedySampler
570 .sample(&[f32::NAN, f32::INFINITY, -2.0], &mut rng)
571 .unwrap();
572 assert_eq!(token.get(), 2);
573 assert!(GreedySampler
574 .sample(&[f32::NAN, f32::INFINITY], &mut rng)
575 .is_err());
576 }
577}
578
579pub struct MultinomialSampler;
581
582impl Sampler for MultinomialSampler {
583 fn sample(&self, logits: &[f32], rng: &mut dyn RngCore) -> Result<TokenId> {
584 let max_logit = logits.iter().fold(f32::NEG_INFINITY, |a, &b| a.max(b));
586
587 let mut probs: Vec<f32> = logits
588 .iter()
589 .map(|&logit| {
590 if logit.is_finite() && logit > f32::NEG_INFINITY {
591 (logit - max_logit).exp()
592 } else {
593 0.0
594 }
595 })
596 .collect();
597
598 let sum: f32 = probs.iter().sum();
599 if sum <= 0.0 {
600 return Err(ferrum_types::FerrumError::backend(
601 "No valid tokens for sampling",
602 ));
603 }
604
605 for prob in probs.iter_mut() {
606 *prob /= sum;
607 }
608
609 let threshold = rng.next_u32() as f32 / u32::MAX as f32;
611 let mut cumulative = 0.0;
612
613 for (idx, prob) in probs.iter().enumerate() {
614 cumulative += prob;
615 if cumulative >= threshold {
616 return Ok(TokenId::new(idx as u32));
617 }
618 }
619
620 Ok(TokenId::new((probs.len() - 1) as u32))
622 }
623
624 fn name(&self) -> &str {
625 "multinomial"
626 }
627
628 fn is_deterministic(&self) -> bool {
629 false
630 }
631}
632
633pub struct SamplingConfigBuilder {
635 processors: Vec<Box<dyn LogitsProcessor>>,
636 sampler: Option<Box<dyn Sampler>>,
637}
638
639impl SamplingConfigBuilder {
640 pub fn new() -> Self {
642 Self {
643 processors: Vec::new(),
644 sampler: None,
645 }
646 }
647
648 pub fn with_temperature(mut self, temperature: f32) -> Self {
650 if temperature > 0.0 && temperature != 1.0 {
651 self.processors
652 .push(Box::new(TemperatureProcessor::new(temperature)));
653 }
654 self
655 }
656
657 pub fn with_top_k(mut self, k: usize) -> Self {
659 if k > 0 {
660 self.processors.push(Box::new(TopKProcessor::new(k)));
661 }
662 self
663 }
664
665 pub fn with_top_p(mut self, p: f32) -> Self {
667 if p > 0.0 && p < 1.0 {
668 self.processors.push(Box::new(TopPProcessor::new(p)));
669 }
670 self
671 }
672
673 pub fn with_min_p(mut self, min_p: f32) -> Self {
675 if min_p > 0.0 && min_p <= 1.0 {
676 self.processors.push(Box::new(MinPProcessor::new(min_p)));
677 }
678 self
679 }
680
681 pub fn with_repetition_penalty(mut self, penalty: f32) -> Self {
683 if penalty != 1.0 {
684 self.processors
685 .push(Box::new(RepetitionPenaltyProcessor::new(penalty)));
686 }
687 self
688 }
689
690 pub fn with_presence_frequency_penalty(
692 mut self,
693 presence_penalty: f32,
694 frequency_penalty: f32,
695 ) -> Self {
696 if presence_penalty != 0.0 || frequency_penalty != 0.0 {
697 self.processors
698 .push(Box::new(PresenceFrequencyPenaltyProcessor::new(
699 presence_penalty,
700 frequency_penalty,
701 )));
702 }
703 self
704 }
705
706 pub fn with_sampler(mut self, sampler: Box<dyn Sampler>) -> Self {
708 self.sampler = Some(sampler);
709 self
710 }
711
712 pub fn build(self) -> SamplingConfig {
714 let mut chain = LogitsProcessorChain::new();
715 for processor in self.processors {
716 chain = chain.add_processor(processor);
717 }
718
719 let sampler = self.sampler.unwrap_or_else(|| Box::new(MultinomialSampler));
720
721 SamplingConfig {
722 processor_chain: chain,
723 sampler,
724 }
725 }
726}
727
728impl Default for SamplingConfigBuilder {
729 fn default() -> Self {
730 Self::new()
731 }
732}
733
734pub struct SamplingConfig {
736 pub processor_chain: LogitsProcessorChain,
737 pub sampler: Box<dyn Sampler>,
738}
739
740impl fmt::Debug for SamplingConfig {
741 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
742 f.debug_struct("SamplingConfig")
743 .field("processor_chain", &self.processor_chain)
744 .field("sampler", &self.sampler.name())
745 .finish()
746 }
747}
748
749impl SamplingConfig {
750 pub fn from_params(params: &SamplingParams) -> Self {
752 let mut builder = SamplingConfigBuilder::new()
753 .with_temperature(params.temperature)
754 .with_repetition_penalty(params.repetition_penalty)
755 .with_presence_frequency_penalty(params.presence_penalty, params.frequency_penalty);
756
757 if let Some(min_p) = params.min_p {
758 builder = builder.with_min_p(min_p);
759 }
760
761 if let Some(top_k) = params.top_k {
762 builder = builder.with_top_k(top_k);
763 }
764
765 if params.top_p < 1.0 {
766 builder = builder.with_top_p(params.top_p);
767 }
768
769 let sampler: Box<dyn Sampler> = if params.temperature == 0.0 {
771 Box::new(GreedySampler)
772 } else {
773 Box::new(MultinomialSampler)
774 };
775
776 builder.with_sampler(sampler).build()
777 }
778
779 pub fn supports_raw_greedy_speculation(&self) -> bool {
784 self.sampler.is_deterministic() && self.processor_chain.is_empty()
785 }
786
787 pub fn sample(&self, mut ctx: SamplingContext, rng: &mut dyn RngCore) -> Result<TokenId> {
789 self.processor_chain.process(&mut ctx)?;
791
792 self.sampler.sample_with_context(&ctx, rng)
794 }
795}
796
797#[derive(Debug, Clone, Serialize, Deserialize)]
799pub struct SamplingStats {
800 pub total_samples: u64,
802 pub avg_sample_time_us: f64,
804 pub token_distribution: HashMap<TokenId, u64>,
806 pub effective_temperature: f32,
808 pub processor_times: HashMap<String, f64>,
810}