1use eredu_core::{capture::*, execution_control::*, TextGenerationDriver, TokenFilterController};
10use eredu_runtime::execution_control::{
11 apply_sampling_override, ManagedTextContinuation, SamplingOverride, SnapshotBudget,
12 SnapshotTokenController, TextBranchRequest, TextContinuationSnapshot,
13 TextSamplingControlBackend, TextSnapshotBackend, TextSnapshotError, TokenChoiceController,
14};
15use std::fmt::Debug;
16
17#[cfg(test)]
18mod tests;
19
20pub struct ContinuationFixtureLimits {
22 pub host_bytes: u64,
24 pub growth_bytes: u64,
26 pub max_predictions: u64,
28 pub capture: Option<CaptureLimits>,
30}
31
32fn step<B: TextSnapshotBackend, C: TokenFilterController>(
33 driver: &mut TextGenerationDriver<'_, B>,
34 state: &mut ManagedTextContinuation<B, C>,
35) -> (u32, Option<CapturedStep>) {
36 let token = state.advance(driver).unwrap().expect("fixture ended early");
37 assert!(matches!(
38 state.boundary(driver),
39 Err(eredu_core::TextContinuationError::NotQuiescent)
40 ));
41 let records = state.take_completed_step(driver).unwrap().map(|mut step| {
42 step.capture_seconds = 0.0;
43 step.cumulative_usage = Default::default();
44 step
45 });
46 (token.token_id(), records)
47}
48
49fn branch_values(mut value: (u32, Option<CapturedStep>)) -> (u32, Option<CapturedStep>) {
50 if let Some(step) = &mut value.1 {
51 for record in &mut step.interventions {
52 record.plan_id.clear();
54 }
55 }
56 value
57}
58
59pub fn sampling_override_conformance<B, C, P>(
63 driver: &mut TextGenerationDriver<'_, B>,
64 state: &mut ManagedTextContinuation<B, C>,
65 limits: &ContinuationFixtureLimits,
66 probe: impl Fn() -> P,
67) where
68 B: TextSnapshotBackend + TextSamplingControlBackend,
69 C: SnapshotTokenController + Clone + PartialEq + Debug,
70 P: PartialEq + Debug,
71{
72 let budget = SnapshotBudget::new(SnapshotLimits {
73 max_snapshots: 2,
74 max_branches: 1,
75 retained_bytes: 64_000_000,
76 cumulative_copy_bytes: 512_000_000,
77 });
78 let initial = TextContinuationSnapshot::capture(
79 &mut state.boundary(driver).unwrap(),
80 &budget,
81 Some(limits.host_bytes),
82 )
83 .unwrap();
84 let facts = B::sampling_control_facts(state.boundary(driver).unwrap().parts().1);
85 let baseline: Vec<_> = (0..3).map(|_| step(driver, state)).collect();
86 initial
87 .restore(&mut state.boundary(driver).unwrap(), &budget)
88 .unwrap();
89 let before = probe();
90 assert!(apply_sampling_override(
91 &mut state.boundary(driver).unwrap(),
92 SamplingOverride {
93 temperature: Some(f32::NAN),
94 reseed: Some(123),
95 }
96 )
97 .is_err());
98 let greedy = apply_sampling_override(
99 &mut state.boundary(driver).unwrap(),
100 SamplingOverride {
101 temperature: Some(0.0),
102 reseed: None,
103 },
104 );
105 if facts.requires_positive_temperature {
106 assert!(greedy.is_err());
107 } else {
108 assert!(
109 greedy.unwrap().has_rng,
110 "temporary greedy selection discarded inherited RNG"
111 );
112 }
113 apply_sampling_override(
114 &mut state.boundary(driver).unwrap(),
115 SamplingOverride {
116 temperature: Some(facts.temperature),
117 reseed: None,
118 },
119 )
120 .unwrap();
121 assert_eq!(probe(), before);
122 let actual: Vec<_> = (0..3).map(|_| step(driver, state)).collect();
123 assert_eq!(
124 actual, baseline,
125 "invalid/no-draw changes lost RNG or sampler state"
126 );
127 initial
128 .restore(&mut state.boundary(driver).unwrap(), &budget)
129 .unwrap();
130 let mut child = initial
131 .fork(
132 &mut state.boundary(driver).unwrap(),
133 &budget,
134 TextBranchRequest {
135 session_id: "conformance-sampling-child",
136 max_predictions: limits.max_predictions,
137 capture_limits: limits.capture.clone(),
138 intervention: None,
139 host_bytes: Some(limits.host_bytes),
140 continuation_growth_bytes: Some(limits.growth_bytes),
141 },
142 )
143 .unwrap();
144 child.exchange(driver, state).unwrap();
145 let before = probe();
146 let updated = apply_sampling_override(
147 &mut state.boundary(driver).unwrap(),
148 SamplingOverride {
149 temperature: Some(0.35),
150 reseed: Some(711),
151 },
152 )
153 .unwrap();
154 assert_eq!(updated.temperature, 0.35);
155 assert!(updated.has_rng);
156 let modified = TextContinuationSnapshot::capture(
157 &mut state.boundary(driver).unwrap(),
158 &budget,
159 Some(limits.host_bytes),
160 )
161 .unwrap();
162 assert_eq!(probe(), before, "sampling change executed model work");
163 let changed: Vec<_> = (0..3).map(|_| step(driver, state)).collect();
164 modified
165 .restore(&mut state.boundary(driver).unwrap(), &budget)
166 .unwrap();
167 assert_eq!(
168 B::sampling_control_facts(state.boundary(driver).unwrap().parts().1),
169 updated
170 );
171 let again: Vec<_> = (0..3).map(|_| step(driver, state)).collect();
172 assert_eq!(
173 again, changed,
174 "snapshot lost explicit reseed or temperature change"
175 );
176 child.exchange(driver, state).unwrap();
177 assert_eq!(
178 B::sampling_control_facts(state.boundary(driver).unwrap().parts().1),
179 facts
180 );
181 let parent: Vec<_> = (0..3).map(|_| step(driver, state)).collect();
182 assert_eq!(parent, baseline, "sampling override changed the parent");
183 initial
184 .restore(&mut state.boundary(driver).unwrap(), &budget)
185 .unwrap();
186}
187
188pub fn forced_choice_conformance<B, C, P>(
193 driver: &mut TextGenerationDriver<'_, B>,
194 state: &mut ManagedTextContinuation<B, TokenChoiceController<C>>,
195 limits: &ContinuationFixtureLimits,
196 vocabulary: usize,
197 probe: impl Fn() -> P,
198) where
199 B: TextSnapshotBackend,
200 C: SnapshotTokenController + Clone + PartialEq + Debug,
201 P: PartialEq + Debug,
202{
203 let budget = SnapshotBudget::new(SnapshotLimits {
204 max_snapshots: 2,
205 max_branches: 1,
206 retained_bytes: 64_000_000,
207 cumulative_copy_bytes: 512_000_000,
208 });
209 let initial = TextContinuationSnapshot::capture(
210 &mut state.boundary(driver).unwrap(),
211 &budget,
212 Some(limits.host_bytes),
213 )
214 .unwrap();
215 let baseline = step(driver, state).0;
216 initial
217 .restore(&mut state.boundary(driver).unwrap(), &budget)
218 .unwrap();
219 let filter = state.controller_mut().current_filter().unwrap();
220 let alternative = (0..vocabulary as u32)
221 .find(|&token| {
222 token != baseline
223 && filter
224 .allowed_mask()
225 .is_none_or(|mask| mask[token as usize])
226 })
227 .expect("fixture must admit an alternative canonical token");
228 let mut expected = state.controller().inner().clone();
229 expected.commit_token(alternative).unwrap();
230 let before = probe();
231 let mut child = initial
232 .fork(
233 &mut state.boundary(driver).unwrap(),
234 &budget,
235 TextBranchRequest {
236 session_id: "conformance-forced-child",
237 max_predictions: limits.max_predictions,
238 capture_limits: limits.capture.clone(),
239 intervention: None,
240 host_bytes: Some(limits.host_bytes),
241 continuation_growth_bytes: Some(limits.growth_bytes),
242 },
243 )
244 .unwrap();
245 child.exchange(driver, state).unwrap();
246 state.controller_mut().force_next(alternative).unwrap();
247 let pending = TextContinuationSnapshot::capture(
248 &mut state.boundary(driver).unwrap(),
249 &budget,
250 Some(limits.host_bytes),
251 )
252 .unwrap();
253 assert_eq!(probe(), before, "choice/fork/capture executed the model");
254 assert_eq!(step(driver, state).0, alternative);
255 assert!(state.controller().last_committed_was_forced());
256 assert_eq!(state.controller().inner(), &expected);
257 let continuation: Vec<_> = (0..3).map(|_| step(driver, state)).collect();
258 assert!(!state.controller().last_committed_was_forced());
259 for _ in 0..2 {
260 let before = probe();
261 pending
262 .restore(&mut state.boundary(driver).unwrap(), &budget)
263 .unwrap();
264 assert_eq!(probe(), before, "restore replayed the forced prefix");
265 assert_eq!(state.controller().pending_forced(), Some(alternative));
266 assert_eq!(step(driver, state).0, alternative);
267 assert_eq!(state.controller().inner(), &expected);
268 let actual: Vec<_> = (0..3).map(|_| step(driver, state)).collect();
269 assert_eq!(actual, continuation);
270 }
271 child.exchange(driver, state).unwrap();
272 assert_eq!(state.controller().pending_forced(), None);
273 assert_eq!(step(driver, state).0, baseline, "child changed the parent");
274 initial
275 .restore(&mut state.boundary(driver).unwrap(), &budget)
276 .unwrap();
277}
278
279pub fn continuation_conformance<B, C, P>(
287 driver: &mut TextGenerationDriver<'_, B>,
288 mut state: ManagedTextContinuation<B, C>,
289 limits: ContinuationFixtureLimits,
290 probe: impl Fn() -> P,
291) where
292 B: TextSnapshotBackend,
293 C: SnapshotTokenController + Clone + PartialEq + Debug,
294 P: PartialEq + Debug,
295{
296 assert!(limits.max_predictions >= 8);
297 let budget = SnapshotBudget::new(SnapshotLimits {
298 max_snapshots: 2,
299 max_branches: 2,
300 retained_bytes: 64_000_000,
301 cumulative_copy_bytes: 512_000_000,
302 });
303 let before = probe();
304 assert!(matches!(
305 TextContinuationSnapshot::capture(&mut state.boundary(driver).unwrap(), &budget, None),
306 Err(TextSnapshotError::Control(
307 ExecutionControlError::UnknownEstimate
308 ))
309 ));
310 assert_eq!(budget.usage(), SnapshotUsage::default());
311 assert_eq!(probe(), before);
312 let initial = TextContinuationSnapshot::capture(
313 &mut state.boundary(driver).unwrap(),
314 &budget,
315 Some(limits.host_bytes),
316 )
317 .unwrap();
318 assert_eq!(initial.next_prediction(), 0);
319 let initial_native = B::estimate_native_text_state(driver.runtime(), None)
320 .unwrap()
321 .unwrap()
322 .retained_bytes;
323 let initial_growth = initial
324 .native_continuation_growth(driver.runtime(), limits.max_predictions)
325 .unwrap();
326 assert_eq!(
327 probe(),
328 before,
329 "growth estimation executed or rebuilt the model"
330 );
331 let prefix: Vec<_> = (0..3).map(|_| step(driver, &mut state)).collect();
332 let prefix_controller = state.controller().clone();
333 let saved = TextContinuationSnapshot::capture(
334 &mut state.boundary(driver).unwrap(),
335 &budget,
336 Some(limits.host_bytes),
337 )
338 .unwrap();
339 assert_eq!(saved.next_prediction(), 3);
340 let usage = budget.usage();
341 let before = probe();
342 assert!(matches!(
343 TextContinuationSnapshot::capture(
344 &mut state.boundary(driver).unwrap(),
345 &budget,
346 Some(limits.host_bytes),
347 ),
348 Err(TextSnapshotError::Control(ExecutionControlError::Limit(
349 "snapshot count"
350 )))
351 ));
352 assert_eq!(budget.usage(), usage);
353 let native_growth = saved
354 .native_continuation_growth(driver.runtime(), limits.max_predictions)
355 .unwrap();
356 let child = |session_id| TextBranchRequest {
357 session_id,
358 max_predictions: limits.max_predictions,
359 capture_limits: limits.capture.clone(),
360 intervention: None,
361 host_bytes: Some(limits.host_bytes),
362 continuation_growth_bytes: Some(limits.growth_bytes.checked_add(native_growth).unwrap()),
363 };
364 let mut left = saved
365 .fork(
366 &mut state.boundary(driver).unwrap(),
367 &budget,
368 child("conformance-left"),
369 )
370 .unwrap();
371 let mut right = saved
372 .fork(
373 &mut state.boundary(driver).unwrap(),
374 &budget,
375 child("conformance-right"),
376 )
377 .unwrap();
378 assert_eq!(probe(), before, "copy/fork executed or rebuilt the model");
379 let baseline: Vec<_> = (0..5).map(|_| step(driver, &mut state)).collect();
380 let native_after = B::estimate_native_text_state(driver.runtime(), None)
381 .unwrap()
382 .unwrap()
383 .retained_bytes;
384 assert!(
385 native_after <= initial_native.checked_add(initial_growth).unwrap(),
386 "retained native state exceeded the pre-generation growth allowance"
387 );
388 left.exchange(driver, &mut state).unwrap();
389 assert!(matches!(
390 saved.restore(&mut state.boundary(driver).unwrap(), &budget),
391 Err(TextSnapshotError::IncompatibleRun)
392 ));
393 if let Some(checkpoint) = saved.capture_checkpoint() {
394 let boundary = state.boundary(driver).unwrap();
395 assert_eq!(
396 B::capture_run(boundary.parts().1)
397 .unwrap()
398 .cumulative_usage(),
399 checkpoint.inherited_usage()
400 );
401 }
402 let first_left = step(driver, &mut state);
403 if let (Some(child), Some(parent)) = (&first_left.1, &baseline[0].1) {
404 for (child, parent) in child.interventions.iter().zip(&parent.interventions) {
405 assert_ne!(
406 child.plan_id, parent.plan_id,
407 "child reused a session-bound admission"
408 );
409 }
410 }
411 assert_eq!(
412 branch_values(first_left),
413 branch_values(baseline[0].clone())
414 );
415 left.exchange(driver, &mut state).unwrap();
416 right.exchange(driver, &mut state).unwrap();
417 let other: Vec<_> = (0..5)
418 .map(|_| branch_values(step(driver, &mut state)))
419 .collect();
420 assert_eq!(
421 other,
422 baseline
423 .iter()
424 .cloned()
425 .map(branch_values)
426 .collect::<Vec<_>>()
427 );
428 right.exchange(driver, &mut state).unwrap();
429 left.exchange(driver, &mut state).unwrap();
430 let rest: Vec<_> = (0..4)
431 .map(|_| branch_values(step(driver, &mut state)))
432 .collect();
433 assert_eq!(
434 rest,
435 baseline[1..]
436 .iter()
437 .cloned()
438 .map(branch_values)
439 .collect::<Vec<_>>()
440 );
441 left.exchange(driver, &mut state).unwrap();
442 let capture_usage = {
443 let boundary = state.boundary(driver).unwrap();
444 B::capture_run(boundary.parts().1).map(|run| run.cumulative_usage())
445 };
446 for _ in 0..2 {
447 let before = probe();
448 saved
449 .restore(&mut state.boundary(driver).unwrap(), &budget)
450 .unwrap();
451 assert_eq!(probe(), before, "restore executed or rebuilt the model");
452 assert_eq!(state.controller(), &prefix_controller);
453 let actual: Vec<_> = (0..5).map(|_| step(driver, &mut state)).collect();
454 assert_eq!(actual, baseline);
455 }
456 if let Some(before) = capture_usage {
457 let boundary = state.boundary(driver).unwrap();
458 let after = B::capture_run(boundary.parts().1)
459 .unwrap()
460 .cumulative_usage();
461 assert!(after.captures >= before.captures);
462 assert!(after.encoded_bytes >= before.encoded_bytes);
463 if before.encoded_bytes != 0 {
464 assert!(after.encoded_bytes > before.encoded_bytes);
465 }
466 }
467 initial
468 .restore(&mut state.boundary(driver).unwrap(), &budget)
469 .unwrap();
470 let actual: Vec<_> = (0..3).map(|_| step(driver, &mut state)).collect();
471 assert_eq!(actual, prefix);
472 drop(right);
473 assert_eq!(budget.usage().branches, 1);
474 left.exchange(driver, &mut state).unwrap();
475 assert!(state.retained_branch_bytes().is_some());
476 let usage = budget.usage();
477 drop(left); assert_eq!(
479 budget.usage(),
480 usage,
481 "dropping parent released active child retention"
482 );
483 drop(state);
484 assert_eq!(budget.usage().branches, 0);
485 drop((initial, saved));
486 assert_eq!(budget.usage().retained_bytes, 0);
487 assert_eq!(budget.usage().snapshots, 0);
488 assert_eq!(
489 budget.usage().cumulative_copy_bytes,
490 usage.cumulative_copy_bytes
491 );
492}