Skip to main content

eredu_evaluation/
execution_control.rs

1//! Reusable continuation checks over the production portable snapshot driver.
2//!
3//! Fixtures supply actual backend mechanisms, an ordinary prepared continuation,
4//! resource bounds and a probe for forward execution, resets and weight loading.
5//! Run with ordinary, captured, intervened and combined admissions. These checks
6//! concern native/sampler/controller/record state; facade semantic conformance is
7//! additional, and must exercise the facade's incremental decoding pipeline.
8
9use 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
20/// Known fixture storage bounds and explicitly admitted child limits.
21pub struct ContinuationFixtureLimits {
22    /// Controller/host storage through the fixture's complete continuation.
23    pub host_bytes: u64,
24    /// Additional native and host growth through the absolute prediction limit.
25    pub growth_bytes: u64,
26    /// Absolute prediction limit, at least eight for this scenario.
27    pub max_predictions: u64,
28    /// Explicit child capture limits, including inherited consumption.
29    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            // Child plans are intentionally re-admitted with fresh identities.
53            record.plan_id.clear();
54        }
55    }
56    value
57}
58
59/// Checks read-only rejection, exact inherited randomness across temperature
60/// changes, and reproducible explicit reseeding in an isolated child. Restores
61/// the initial parent afterward without model replay.
62pub 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
188/// Exercises a genuinely alternative canonical choice, isolated branch commitment,
189/// and a reusable snapshot with a still-pending choice. The fixture must allow at
190/// least two tokens at its initial decision. Restores the initial state afterward,
191/// so the same run can enter `continuation_conformance` without replay.
192pub 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
279/// Checks reusable initial/decode snapshots, pending-input position, exact sampled
280/// continuations, controller restoration, isolated interleaved siblings, fresh
281/// child admissions, cumulative capture accounting and retention-lease ownership.
282/// `probe` must change on model forward, reset, artifact reopen or weight reload;
283/// it must not count copying. Nonzero-temperature/adaptive fixtures detect lost
284/// RNG/history state. This function consumes the initial continuation for cleanup
285/// checks; its driver remains usable only for inspecting the fixture afterward.
286pub 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); // This slot now holds the original parent, not the charged child.
478    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}