eredu_runtime/execution_control/
sampling.rs1use eredu_core::{
3 ModelRuntime, TextContinuationBoundary, TextGenerationBackend, TokenFilterController,
4};
5use serde::{Deserialize, Serialize};
6
7#[derive(Debug, Clone, Copy, Default, PartialEq, Serialize, Deserialize)]
11pub struct SamplingOverride {
12 pub temperature: Option<f32>,
14 pub reseed: Option<u64>,
16}
17
18#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
20pub struct SamplingStateFacts {
21 pub temperature: f32,
23 pub requires_positive_temperature: bool,
25 pub has_rng: bool,
27}
28
29#[derive(Debug, Clone, Copy)]
31pub struct ValidatedSamplingOverride {
32 temperature: f32,
33 reseed: Option<u64>,
34}
35impl ValidatedSamplingOverride {
36 pub fn temperature(self) -> f32 {
38 self.temperature
39 }
40 pub fn reseed(self) -> Option<u64> {
42 self.reseed
43 }
44}
45
46pub trait TextSamplingControlBackend: TextGenerationBackend {
49 fn sampling_control_facts(state: &Self::TextGenerationState) -> SamplingStateFacts;
51 fn install_sampling_override(
55 runtime: &mut ModelRuntime<Self>,
56 state: &mut Self::TextGenerationState,
57 request: ValidatedSamplingOverride,
58 ) -> Result<(), Self::Error>;
59}
60
61#[derive(Debug, thiserror::Error)]
63pub enum SamplingOverrideError<E: std::error::Error + 'static> {
64 #[error("invalid sampling override: {0}")]
66 Invalid(&'static str),
67 #[error("native sampling override failed: {0}")]
69 Backend(#[source] E),
70}
71
72fn validate<E: std::error::Error + 'static>(
73 facts: SamplingStateFacts,
74 request: SamplingOverride,
75) -> Result<ValidatedSamplingOverride, SamplingOverrideError<E>> {
76 let temperature = request.temperature.unwrap_or(facts.temperature);
77 if !temperature.is_finite() || temperature < 0.0 {
78 return Err(SamplingOverrideError::Invalid(
79 "temperature must be finite and nonnegative",
80 ));
81 }
82 if facts.requires_positive_temperature && temperature == 0.0 {
83 return Err(SamplingOverrideError::Invalid(
84 "adaptive sampling requires positive temperature",
85 ));
86 }
87 if temperature > 0.0 && !facts.has_rng && request.reseed.is_none() {
88 return Err(SamplingOverrideError::Invalid(
89 "no inherited RNG exists; an explicit seed is required",
90 ));
91 }
92 Ok(ValidatedSamplingOverride {
93 temperature,
94 reseed: request.reseed,
95 })
96}
97
98pub fn apply_sampling_override<B: TextSamplingControlBackend, C: TokenFilterController>(
102 boundary: &mut TextContinuationBoundary<'_, '_, B, C>,
103 request: SamplingOverride,
104) -> Result<SamplingStateFacts, SamplingOverrideError<B::Error>> {
105 let (runtime, state, _) = boundary.mechanism_parts();
106 apply_prepared_sampling_override(runtime, state, request)
107}
108
109pub fn apply_prepared_sampling_override<B: TextSamplingControlBackend>(
114 runtime: &mut ModelRuntime<B>,
115 state: &mut B::TextGenerationState,
116 request: SamplingOverride,
117) -> Result<SamplingStateFacts, SamplingOverrideError<B::Error>> {
118 if let eredu_core::execution_control::ControlSupport::Unsupported { .. } =
119 B::text_sampling_control_support(runtime)
120 {
121 return Err(SamplingOverrideError::Invalid(
122 "loaded execution does not support sampling overrides",
123 ));
124 }
125 let action = validate(B::sampling_control_facts(state), request)?;
126 B::install_sampling_override(runtime, state, action).map_err(SamplingOverrideError::Backend)?;
127 Ok(B::sampling_control_facts(state))
128}
129
130#[cfg(test)]
131mod tests {
132 use super::*;
133 fn check(
134 facts: SamplingStateFacts,
135 request: SamplingOverride,
136 ) -> Result<ValidatedSamplingOverride, SamplingOverrideError<std::io::Error>> {
137 validate(facts, request)
138 }
139 #[test]
140 fn temperature_changes_preserve_rng_and_adaptation_unless_reseeding_is_explicit() {
141 let initial = SamplingStateFacts {
142 temperature: 0.0,
143 requires_positive_temperature: false,
144 has_rng: false,
145 };
146 let stochastic = SamplingOverride {
147 temperature: Some(0.7),
148 reseed: None,
149 };
150 assert!(check(initial, stochastic).is_err());
151 let seeded = check(
152 initial,
153 SamplingOverride {
154 reseed: Some(42),
155 ..stochastic
156 },
157 )
158 .unwrap();
159 assert_eq!(seeded.reseed(), Some(42));
160 assert_eq!(seeded.temperature(), 0.7);
161 let retained = SamplingStateFacts {
162 has_rng: true,
163 ..initial
164 };
165 assert_eq!(check(retained, stochastic).unwrap().reseed(), None);
166 for invalid in [f32::NAN, f32::INFINITY, -1.0] {
167 assert!(check(
168 retained,
169 SamplingOverride {
170 temperature: Some(invalid),
171 reseed: None
172 }
173 )
174 .is_err());
175 }
176 let adaptive = SamplingStateFacts {
177 temperature: 0.8,
178 requires_positive_temperature: true,
179 has_rng: true,
180 };
181 assert!(check(
182 adaptive,
183 SamplingOverride {
184 temperature: Some(0.0),
185 reseed: None
186 }
187 )
188 .is_err());
189 assert_eq!(
190 check(adaptive, SamplingOverride::default())
191 .unwrap()
192 .temperature(),
193 0.8
194 );
195 }
196}