Skip to main content

eredu_runtime/
inspection.rs

1//! Backend-neutral activation, target-state, and routed-expert observation contracts.
2
3use std::collections::{BTreeMap, BTreeSet};
4
5/// Combines explicit architecture intervention declarations with exact loaded
6/// observation support and backend arithmetic facts. Read-only catalog values
7/// never acquire intervention capabilities through this projection.
8pub fn intervention_support(
9    mut points: Vec<eredu_core::intervention::InterventionPoint>,
10    capture: &eredu_core::capture::CaptureDiscovery,
11    mechanisms: &eredu_core::intervention::InterventionMechanisms,
12) -> eredu_core::intervention::InterventionDiscovery {
13    use eredu_core::{intervention::*, ObservationSupportStatus as S};
14    for point in &mut points {
15        point
16            .operations
17            .retain(|kind| mechanisms.operations.contains(kind));
18        point
19            .dtypes
20            .retain(|dtype| mechanisms.dtypes.contains(dtype));
21        point
22            .score_stages
23            .retain(|stage| mechanisms.score_stages.contains(stage));
24        let path = if point.routing.is_some() {
25            eredu_core::RoutingObservationField::SelectedExperts.path(&point.path)
26        } else {
27            point.path.clone()
28        };
29        if let Some(support) = capture.support.points.iter().find(|p| p.path == path) {
30            point.prefill = support.prefill.clone();
31            point.decode = support.decode.clone();
32        }
33        if point.axes.is_empty()
34            || point.operations.is_empty()
35            || (point.routing.is_none() && point.dtypes.is_empty())
36        {
37            point.prefill = S::Unsupported(
38                "required intervention geometry or native mechanism is not declared".into(),
39            );
40            point.decode = point.prefill.clone();
41        }
42    }
43    InterventionDiscovery {
44        schema_version: INTERVENTION_SCHEMA_VERSION,
45        artifact_identity: capture.artifact_identity.clone(),
46        session_identity: None,
47        points,
48    }
49}
50
51/// Selected, model-independent conditions used to report capture support.
52#[derive(Debug, Clone, Copy)]
53pub struct ObservationExecutionContext {
54    /// Exact admitted session enables the instrumented execution route.
55    pub activation_inspection: bool,
56    /// Rank ownership and provider-specific routing need more precise discovery.
57    pub partitioned: bool,
58    /// An execution configuration was successfully selected.
59    pub selected: bool,
60    /// Side-effect-free native collector facts.
61    pub mechanisms: eredu_core::ObservationMechanisms,
62}
63
64/// Resolves support without modifying the logical graph or inventing observations.
65pub fn observation_support(
66    catalog: &eredu_core::ObservationCatalog,
67    context: ObservationExecutionContext,
68) -> eredu_core::ObservationSupportReport {
69    eredu_core::ObservationSupportReport {
70        schema_version: eredu_core::DISCOVERY_SCHEMA_VERSION,
71        capture: Default::default(),
72        points: catalog
73            .points
74            .iter()
75            .map(|point| eredu_core::ObservationSupport {
76                path: point.path.clone(),
77                prefill: point_support(point, point.prefill, context),
78                decode: point_support(point, point.decode, context),
79                floating_to_f32: context.mechanisms.floating_to_f32,
80            })
81            .collect(),
82    }
83}
84
85fn point_support(
86    point: &eredu_core::ObservationPoint,
87    phase_available: bool,
88    context: ObservationExecutionContext,
89) -> eredu_core::ObservationSupportStatus {
90    use eredu_core::{ObservationRequirement as R, ObservationSupportStatus as S};
91    if !phase_available {
92        return S::Unsupported("The architecture does not emit this point in this phase".into());
93    }
94    if !context.selected {
95        return S::Unverified("No admitted execution configuration".into());
96    }
97    if !context.activation_inspection {
98        return S::Unsupported("Selected session does not enable activation inspection".into());
99    }
100    if !context.mechanisms.activation_tensors {
101        return S::Unsupported("Backend has not declared tensor capture support".into());
102    }
103    if point.requirements.contains(&R::RoutingEvents) && !context.mechanisms.routing_tensors {
104        return S::Unsupported("Backend does not collect normalized routing events".into());
105    }
106    if context.partitioned {
107        return S::Unverified(
108            "Partition-local ownership and routing observation coverage are not yet described"
109                .into(),
110        );
111    }
112    if point.requirements.contains(&R::MediaInput) {
113        return S::Conditional("Requires the corresponding media input during prefill".into());
114    }
115    if point.requirements.contains(&R::PredictionExecution) {
116        return S::Conditional("Requires the corresponding prediction execution group".into());
117    }
118    S::Supported
119}
120
121/// One ordinary block output selected for a target/draft consumer.
122pub struct TargetStateTap<'a, T> {
123    /// Architecture block ordinal.
124    pub layer: usize,
125    /// Backend-native block output.
126    pub value: &'a T,
127}
128
129/// Ordered target-state capture without backend storage or family policy.
130#[derive(Debug, Clone)]
131pub struct TargetStateCapture<T> {
132    requested: Vec<usize>,
133    captured: BTreeMap<usize, T>,
134}
135
136impl<T> TargetStateCapture<T> {
137    /// Creates a capture plan with exact, ordered layer identities.
138    pub fn new(
139        requested: impl IntoIterator<Item = usize>,
140    ) -> Result<Self, TargetStateCaptureError> {
141        let requested = requested.into_iter().collect::<Vec<_>>();
142        if requested.is_empty() {
143            return Err(TargetStateCaptureError::Empty);
144        }
145        let mut unique = BTreeSet::new();
146        if let Some(duplicate) = requested.iter().find(|layer| !unique.insert(**layer)) {
147            return Err(TargetStateCaptureError::DuplicateRequest(*duplicate));
148        }
149        Ok(Self {
150            requested,
151            captured: BTreeMap::new(),
152        })
153    }
154
155    /// Returns whether this plan requests one block output.
156    pub fn wants(&self, layer: usize) -> bool {
157        self.requested.contains(&layer)
158    }
159
160    /// Captures one requested block output exactly once.
161    pub fn capture(&mut self, tap: TargetStateTap<'_, T>) -> Result<(), TargetStateCaptureError>
162    where
163        T: Clone,
164    {
165        if !self.wants(tap.layer) {
166            return Err(TargetStateCaptureError::Unrequested(tap.layer));
167        }
168        if self.captured.insert(tap.layer, tap.value.clone()).is_some() {
169            return Err(TargetStateCaptureError::DuplicateCapture(tap.layer));
170        }
171        Ok(())
172    }
173
174    /// Returns captured values in declared request order, rejecting omissions.
175    pub fn into_ordered(mut self) -> Result<Vec<T>, TargetStateCaptureError> {
176        let mut ordered = Vec::with_capacity(self.requested.len());
177        for layer in self.requested {
178            ordered.push(
179                self.captured
180                    .remove(&layer)
181                    .ok_or(TargetStateCaptureError::Missing(layer))?,
182            );
183        }
184        Ok(ordered)
185    }
186}
187
188/// Invalid target-state capture lifecycle.
189#[derive(Debug, Clone, Eq, PartialEq, thiserror::Error)]
190pub enum TargetStateCaptureError {
191    /// At least one state tap must be requested.
192    #[error("target-state capture requires at least one layer")]
193    Empty,
194    /// One layer was requested more than once.
195    #[error("target-state layer {0} was requested more than once")]
196    DuplicateRequest(usize),
197    /// A block emitted a state that was not requested.
198    #[error("target-state layer {0} was not requested")]
199    Unrequested(usize),
200    /// A requested state was captured more than once.
201    #[error("target-state layer {0} was captured more than once")]
202    DuplicateCapture(usize),
203    /// A requested block never emitted its output.
204    #[error("target-state layer {0} was not captured")]
205    Missing(usize),
206}
207
208/// Normalized routed-expert data emitted by architecture implementations.
209pub struct RoutingObservation<'a, T> {
210    /// Stable path-like name of the routed block.
211    pub path: &'a str,
212    /// Selected expert IDs shaped `[..., top_k]`.
213    pub selected_experts: &'a T,
214    /// Selected scores before optional top-k renormalization.
215    pub selected_scores: &'a T,
216    /// Final route weights applied to expert outputs.
217    pub coefficients: &'a T,
218    /// Combined routed expert contribution.
219    pub routed_output: &'a T,
220    /// Rank-local contribution before expert-parallel reduction.
221    pub local_routed_output: Option<&'a T>,
222    /// Globally reduced expert-parallel contribution.
223    pub reduced_routed_output: Option<&'a T>,
224    /// Shared-expert contribution when the architecture has one.
225    pub shared_output: Option<&'a T>,
226    /// Combined routed and shared contribution when reported separately.
227    pub combined_output: Option<&'a T>,
228    /// Total number of routed experts.
229    pub expert_count: i32,
230}
231
232impl<T> RoutingObservation<'_, T> {
233    /// Enumerates only present event fields using the same typed paths as discovery.
234    pub fn for_each_tensor(&self, mut visit: impl FnMut(String, &T)) {
235        use eredu_core::RoutingObservationField as Field;
236        for (field, value) in [
237            (Field::SelectedExperts, Some(self.selected_experts)),
238            (Field::SelectedScores, Some(self.selected_scores)),
239            (Field::Coefficients, Some(self.coefficients)),
240            (Field::RoutedOutput, Some(self.routed_output)),
241            (Field::LocalRoutedOutput, self.local_routed_output),
242            (Field::ReducedRoutedOutput, self.reduced_routed_output),
243            (Field::SharedOutput, self.shared_output),
244            (Field::CombinedOutput, self.combined_output),
245        ] {
246            if let Some(value) = value {
247                visit(field.path(self.path), value);
248            }
249        }
250    }
251}
252
253/// Statically dispatched activation observation and intervention contract.
254pub trait ActivationObserver<T, E> {
255    /// Obtains a validated control before the selector dispatches any experts.
256    /// The default ordinary path allocates and materializes nothing.
257    fn routing_control(
258        &mut self,
259        _path: &str,
260        _token_rows: u64,
261    ) -> Result<Option<eredu_nn::routing_intervention::GroupSelectionControl>, E> {
262        Ok(None)
263    }
264
265    /// Receives original/effective decisions before they reach the expert provider.
266    fn routing_applied(
267        &mut self,
268        _path: &str,
269        _original: Option<RoutingDecision<'_, T>>,
270        _effective: RoutingDecision<'_, T>,
271    ) -> Result<(), E> {
272        Ok(())
273    }
274
275    /// Records a failed control under the enclosing forward pass's failure owner.
276    fn routing_failed(&mut self, _path: &str, _message: &str) {}
277
278    /// Completes an ordinary forward pass under its existing failure owner.
279    /// Scheduled-but-missing interventions must fail before prediction commitment.
280    fn finish(&mut self) -> Result<(), E> {
281        Ok(())
282    }
283
284    /// Observes a named backend-native tensor.
285    fn observe(&mut self, path: &str, value: &T) -> Result<(), E>;
286
287    /// Optionally replaces an activation before it is consumed or returned.
288    fn intervene(&mut self, _path: &str, _value: &T) -> Result<Option<T>, E> {
289        Ok(None)
290    }
291
292    /// Observes normalized routed-expert decisions and contributions.
293    fn observe_routing(&mut self, _routing: RoutingObservation<'_, T>) -> Result<(), E> {
294        Ok(())
295    }
296}
297
298/// Borrowed pre-dispatch decision, independent of expert outputs or shared experts.
299pub struct RoutingDecision<'a, T> {
300    /// Exact global expert IDs shaped `[token_rows, top_k]`.
301    pub ids: &'a T,
302    /// Final coefficients used for dispatch.
303    pub coefficients: &'a T,
304}
305
306impl<'a, T> From<&'a eredu_nn::GroupSelection<T>> for RoutingDecision<'a, T> {
307    fn from(routes: &'a eredu_nn::GroupSelection<T>) -> Self {
308        Self {
309            ids: routes.group_indices(),
310            coefficients: routes.coefficients(),
311        }
312    }
313}
314
315/// Observes an activation and applies an optional replacement without
316/// materializing its backend-native value.
317pub fn observe_and_intervene<T, E, O>(observer: &mut O, path: &str, value: &T) -> Result<T, E>
318where
319    T: Clone,
320    O: ActivationObserver<T, E> + ?Sized,
321{
322    observer.observe(path, value)?;
323    Ok(observer
324        .intervene(path, value)?
325        .unwrap_or_else(|| value.clone()))
326}
327
328/// Observes final model logits and applies an optional replacement.
329///
330/// Family and topology adapters must return this value, rather than merely
331/// reporting [`eredu_core::MODEL_LOGITS_OBSERVATION_PATH`], so final-output
332/// intervention has the same semantics as every other activation point.
333pub fn observe_model_logits<T, E, O>(observer: &mut O, logits: &T) -> Result<T, E>
334where
335    T: Clone,
336    O: ActivationObserver<T, E> + ?Sized,
337{
338    observe_and_intervene(observer, eredu_core::MODEL_LOGITS_OBSERVATION_PATH, logits)
339}
340
341/// Zero-sized observer used by the ordinary unobserved inference path.
342#[derive(Debug, Default, Clone, Copy)]
343pub struct NoopObserver;
344
345impl<T, E> ActivationObserver<T, E> for NoopObserver {
346    fn observe(&mut self, _path: &str, _value: &T) -> Result<(), E> {
347        Ok(())
348    }
349}
350
351impl<T, E, F> ActivationObserver<T, E> for F
352where
353    F: FnMut(&str, &T) -> Result<(), E>,
354{
355    fn observe(&mut self, path: &str, value: &T) -> Result<(), E> {
356        self(path, value)
357    }
358}
359
360#[cfg(test)]
361mod tests {
362    use super::*;
363
364    #[test]
365    fn noop_observer_is_static_and_passthrough() {
366        fn observe<O: ActivationObserver<i32, ()>>(observer: &mut O) {
367            observer.observe("layer.output", &7).unwrap();
368            assert_eq!(observer.intervene("layer.output", &7).unwrap(), None);
369        }
370        observe(&mut NoopObserver);
371    }
372
373    #[test]
374    fn observed_activation_can_be_replaced_without_an_erased_hot_path() {
375        struct ReplacingObserver {
376            observed: Vec<String>,
377        }
378
379        impl ActivationObserver<i32, ()> for ReplacingObserver {
380            fn observe(&mut self, path: &str, _value: &i32) -> Result<(), ()> {
381                self.observed.push(path.into());
382                Ok(())
383            }
384
385            fn intervene(&mut self, path: &str, value: &i32) -> Result<Option<i32>, ()> {
386                Ok((path == "model.layers.0.output").then_some(value + 4))
387            }
388        }
389
390        let mut observer = ReplacingObserver {
391            observed: Vec::new(),
392        };
393        let output = observe_and_intervene(&mut observer, "model.layers.0.output", &3).unwrap();
394        assert_eq!(output, 7);
395        assert_eq!(observer.observed, ["model.layers.0.output"]);
396    }
397
398    #[test]
399    fn final_logits_observation_returns_the_intervention() {
400        struct ReplacingLogits;
401
402        impl ActivationObserver<i32, ()> for ReplacingLogits {
403            fn observe(&mut self, path: &str, value: &i32) -> Result<(), ()> {
404                assert_eq!(path, eredu_core::MODEL_LOGITS_OBSERVATION_PATH);
405                assert_eq!(*value, 3);
406                Ok(())
407            }
408
409            fn intervene(&mut self, path: &str, value: &i32) -> Result<Option<i32>, ()> {
410                assert_eq!(path, eredu_core::MODEL_LOGITS_OBSERVATION_PATH);
411                Ok(Some(value + 4))
412            }
413        }
414
415        assert_eq!(observe_model_logits(&mut ReplacingLogits, &3), Ok(7));
416    }
417
418    #[test]
419    fn target_states_are_captured_once_in_request_order() {
420        let mut capture = TargetStateCapture::new([5, 1, 3]).unwrap();
421        assert!(capture.wants(1));
422        assert!(!capture.wants(2));
423        capture
424            .capture(TargetStateTap {
425                layer: 1,
426                value: &10,
427            })
428            .unwrap();
429        capture
430            .capture(TargetStateTap {
431                layer: 5,
432                value: &50,
433            })
434            .unwrap();
435        capture
436            .capture(TargetStateTap {
437                layer: 3,
438                value: &30,
439            })
440            .unwrap();
441        assert_eq!(capture.into_ordered().unwrap(), [50, 10, 30]);
442    }
443
444    #[test]
445    fn target_state_capture_rejects_duplicates_omissions_and_unrequested_layers() {
446        assert_eq!(
447            TargetStateCapture::<i32>::new([2, 2]).unwrap_err(),
448            TargetStateCaptureError::DuplicateRequest(2)
449        );
450        let mut capture = TargetStateCapture::new([2]).unwrap();
451        assert_eq!(
452            capture
453                .capture(TargetStateTap {
454                    layer: 3,
455                    value: &7,
456                })
457                .unwrap_err(),
458            TargetStateCaptureError::Unrequested(3)
459        );
460        assert_eq!(
461            capture.into_ordered().unwrap_err(),
462            TargetStateCaptureError::Missing(2)
463        );
464    }
465}