Skip to main content

eredu_core/
discovery.rs

1//! Versioned logical architecture and capture discovery, independent of execution storage.
2//!
3//! Node IDs, canonical parameter group prefixes, and activation paths are separate
4//! namespaces. A node need not have an observation point. `None` means unknown,
5//! never zero; completeness describes omissions at the stated scope.
6
7use serde::{Deserialize, Serialize};
8
9/// Current wire schema for architecture and observation discovery.
10pub const DISCOVERY_SCHEMA_VERSION: u32 = 1;
11
12/// Coverage of a descriptor, node, or catalog.
13#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
14#[serde(tag = "status", content = "reasons", rename_all = "snake_case")]
15pub enum DescriptionCompleteness {
16    /// All details in the documented logical abstraction are represented.
17    Complete,
18    /// Known omissions; represented facts remain authoritative.
19    Partial(Vec<String>),
20    /// No supported description at this scope.
21    Unsupported(Vec<String>),
22}
23
24/// Semantic dimension; symbols refer to the current operation, not a fixed batch.
25#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
26#[serde(tag = "kind", content = "value", rename_all = "snake_case")]
27pub enum SymbolicDimension {
28    /// An exact positive or zero extent.
29    Known(usize),
30    /// Batch size of the current operation.
31    Batch,
32    /// Sequence length of this operation, usually one for decode.
33    Sequence,
34    /// Flattened batch times sequence rows, used by expert routing tensors.
35    TokenRows,
36    /// Visible prefix length including cached positions.
37    Context,
38    /// Input-dependent media feature positions.
39    MediaPositions,
40    /// Extent is not described.
41    Unknown,
42}
43
44/// Named tensor axis in storage order.
45#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
46pub struct TensorAxis {
47    /// Semantic axis name; independent of checkpoint field spelling.
48    pub name: String,
49    /// Known extent or operation-dependent symbolic dimension.
50    pub dimension: SymbolicDimension,
51}
52
53/// Operation class, interpreted without matching a model-family name.
54#[derive(Debug, Clone, Copy, Eq, PartialEq, Serialize, Deserialize)]
55#[serde(rename_all = "snake_case")]
56#[non_exhaustive]
57pub enum ArchitectureNodeKind {
58    /// Token or codebook embedding lookup.
59    Embedding,
60    /// One architecture-defined decoder execution unit.
61    DecoderBlock,
62    /// Normalization of activations.
63    Normalization,
64    /// An attention operation.
65    Attention,
66    /// A stateful token mixer.
67    Mixer,
68    /// Dense feed-forward computation.
69    FeedForward,
70    /// Routed and optional shared expert computation.
71    MixtureOfExperts,
72    /// Expert selection and coefficient computation.
73    Router,
74    /// Bank of selected experts.
75    RoutedExperts,
76    /// Always-on expert branch.
77    SharedExperts,
78    /// Addition of a sublayer contribution and its bypass input.
79    ResidualAdd,
80    /// Combination of parallel branch contributions.
81    Sum,
82    /// Projection into an output domain such as vocabulary logits.
83    OutputHead,
84    /// Host or tensor input processing.
85    Processor,
86    /// Media feature encoder.
87    Encoder,
88    /// Projection of encoded features into decoder space.
89    Projector,
90    /// Assembly of text and media features.
91    ModalityMerge,
92    /// Checkpoint-embedded prediction component.
93    Prediction,
94    /// Temporal/depth frame execution.
95    Realtime,
96    /// Operation whose internal semantics are not described.
97    Opaque,
98}
99
100/// Query/key-value head sharing, independent of receptive field and mechanism.
101#[derive(Debug, Clone, Copy, Eq, PartialEq, Serialize, Deserialize)]
102#[serde(rename_all = "snake_case")]
103pub enum HeadSharing {
104    /// One key/value head per query head.
105    MultiHead,
106    /// A single shared key/value head.
107    MultiQuery,
108    /// Multiple query heads share each of several key/value heads.
109    GroupedQuery,
110}
111
112/// Reach of one attention operation.
113#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
114#[serde(tag = "kind", rename_all = "snake_case")]
115pub enum ReceptiveField {
116    /// The complete causal prefix.
117    Full,
118    /// A bounded trailing window.
119    Sliding {
120        /// Maximum number of visible positions.
121        window: usize,
122    },
123    /// A local neighborhood.
124    Local {
125        /// Neighborhood size, when known.
126        window: Option<usize>,
127    },
128}
129
130/// Attention equation class; recurrent linear attention can use both flags below.
131#[derive(Debug, Clone, Copy, Eq, PartialEq, Serialize, Deserialize)]
132#[serde(rename_all = "snake_case")]
133pub enum AttentionMechanism {
134    /// Softmax over all candidate scores.
135    Softmax,
136    /// Linear attention with optional recurrent state.
137    Linear,
138    /// Attention over compressed latent key/value representations.
139    Latent,
140    /// Attention over local and compressed sparse history.
141    CompressedSparse,
142}
143
144/// Positional encoding class. Exact scaling may remain outside this abstraction.
145#[derive(Debug, Clone, Copy, Eq, PartialEq, Serialize, Deserialize)]
146#[serde(rename_all = "snake_case")]
147pub enum PositionalEncoding {
148    /// Rotary position encoding.
149    Rotary,
150    /// Relative positional features.
151    Relative,
152    /// Learned positional embeddings.
153    Learned,
154    /// No such transformation is applied.
155    None,
156}
157
158/// Independent optional semantic facts for one layer's attention.
159#[derive(Debug, Clone, Default, Eq, PartialEq, Serialize, Deserialize)]
160pub struct AttentionAttributes {
161    /// Relationship between query heads and key/value heads, when applicable.
162    pub head_sharing: Option<HeadSharing>,
163    /// Number of query heads.
164    pub query_heads: Option<usize>,
165    /// Number of key/value heads.
166    pub key_value_heads: Option<usize>,
167    /// Per-head query/key width.
168    pub key_head_dimension: Option<usize>,
169    /// Per-head value width.
170    pub value_head_dimension: Option<usize>,
171    /// Positions visible to this layer; unknown for mixed compressed-history policies.
172    pub receptive_field: Option<ReceptiveField>,
173    /// Equation class of this operator.
174    pub mechanism: Option<AttentionMechanism>,
175    /// Whether execution maintains recurrent state.
176    pub recurrent: Option<bool>,
177    /// Whether future positions are masked.
178    pub causal: Option<bool>,
179    /// Position encoding used by this layer.
180    pub positional_encoding: Option<PositionalEncoding>,
181}
182
183/// Stateful token-mixing equation class.
184#[derive(Debug, Clone, Copy, Eq, PartialEq, Serialize, Deserialize)]
185#[serde(rename_all = "snake_case")]
186pub enum MixerMechanism {
187    /// Gated delta recurrent attention.
188    GatedDelta,
189    /// Selective state-space mixer.
190    SelectiveStateSpace,
191    /// Gated causal short convolution.
192    ShortConvolution,
193}
194
195/// Optional geometry for a stateful token mixer.
196#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
197pub struct MixerAttributes {
198    /// Equation class of this operator.
199    pub mechanism: MixerMechanism,
200    /// Whether execution maintains recurrent state.
201    pub recurrent: bool,
202    /// Causal convolution kernel width, when present.
203    pub convolution_width: Option<usize>,
204}
205
206/// Semantic granularity of expert selection.
207#[derive(Debug, Clone, Copy, Eq, PartialEq, Serialize, Deserialize)]
208#[serde(rename_all = "snake_case")]
209pub enum RoutingGranularity {
210    /// One decision per token.
211    Token,
212    /// One decision per sequence.
213    Sequence,
214}
215
216/// Router score transformation, independent of top-k normalization.
217#[derive(Debug, Clone, Copy, Eq, PartialEq, Serialize, Deserialize)]
218#[serde(rename_all = "snake_case")]
219pub enum RoutingScoreTransform {
220    /// Softmax over all candidate scores.
221    Softmax,
222    /// Softmax applied only after top-k selection.
223    SelectedSoftmax,
224    /// Independent sigmoid-transformed scores.
225    Sigmoid,
226    /// Square root of softplus-transformed scores.
227    SqrtSoftplus,
228    /// Untransformed scores.
229    Identity,
230}
231
232/// Normalization policy for selected route coefficients.
233#[derive(Debug, Clone, Copy, Eq, PartialEq, Serialize, Deserialize)]
234#[serde(rename_all = "snake_case")]
235pub enum RoutingNormalization {
236    /// Selected scores are normalized by their sum.
237    SelectedSum,
238    /// Joint normalization over selected routed experts and always-on experts.
239    SelectedAndSharedSum,
240    /// No such transformation is applied.
241    None,
242}
243
244/// Routed and always-on experts are distinct branches, also linked by graph edges.
245#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
246pub struct MoeAttributes {
247    /// Number of independently routed experts.
248    pub routed_experts: usize,
249    /// Number of experts selected per routing decision.
250    pub selected_experts: usize,
251    /// Number of always-on experts; zero explicitly means none.
252    pub shared_experts: Option<usize>,
253    /// Intermediate width of the shared branch, when known.
254    pub shared_expert_width: Option<usize>,
255    /// Whether a learned gate scales the shared contribution.
256    pub shared_expert_gated: Option<bool>,
257    /// Unit on which expert decisions are made.
258    pub granularity: Option<RoutingGranularity>,
259    /// Transformation of router logits into scores.
260    pub score_transform: Option<RoutingScoreTransform>,
261    /// Normalization applied after selecting experts.
262    pub normalization: Option<RoutingNormalization>,
263}
264
265/// A semantic node; containment is separate from data-flow edges.
266#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
267pub struct ArchitectureNode {
268    /// Stable identity within this descriptor.
269    pub id: String,
270    /// Human-readable operation label.
271    pub label: String,
272    /// Typed semantic category.
273    pub kind: ArchitectureNodeKind,
274    /// Containing node, independent of data-flow dependencies.
275    pub parent: Option<String>,
276    /// Zero-based physical layer ordinal, if this node is a decoder unit.
277    pub layer_index: Option<usize>,
278    /// Canonical parameter-group identities referenced by this node.
279    pub parameter_groups: Vec<String>,
280    /// Exact activation selectors associated with this node.
281    pub observation_paths: Vec<String>,
282    /// Output axes in storage order; absent when rank or shape semantics are unknown.
283    pub output_axes: Option<Vec<TensorAxis>>,
284    /// Independent attention properties for this exact layer.
285    pub attention: Option<AttentionAttributes>,
286    /// Stateful mixer properties, when applicable.
287    pub mixer: Option<MixerAttributes>,
288    /// Expert topology and routing policy, when applicable.
289    pub moe: Option<MoeAttributes>,
290    /// Explicit omissions at this scope.
291    pub completeness: DescriptionCompleteness,
292}
293
294/// Semantic role of a logical graph edge.
295#[derive(Debug, Clone, Copy, Eq, PartialEq, Serialize, Deserialize)]
296#[serde(rename_all = "snake_case")]
297pub enum ArchitectureEdgeKind {
298    /// Ordinary activation flow.
299    Data,
300    /// Bypass activation consumed by a residual join.
301    Residual,
302    /// Expert-selection control flow.
303    Routing,
304    /// Persistent state dependency.
305    State,
306}
307
308/// Directed logical data flow, including the bypass input of a residual addition.
309#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
310pub struct ArchitectureEdge {
311    /// Source node identity.
312    pub from: String,
313    /// Destination node identity.
314    pub to: String,
315    /// Typed semantic category.
316    pub kind: ArchitectureEdgeKind,
317}
318
319/// Architecture-declared canonical parameter namespace, not an activation selector.
320/// Physical checkpoint aliases and encoding companions remain in checkpoint schemas.
321#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
322pub struct ArchitectureParameterGroup {
323    /// Stable identity within this descriptor.
324    pub id: String,
325    /// Canonical logical checkpoint module prefix; not a physical source key or activation selector.
326    pub canonical_prefix: String,
327}
328
329/// Backend-independent logical graph from an admitted architecture plan.
330#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
331pub struct ArchitectureDescriptor {
332    /// Version of the discovery wire schema.
333    pub schema_version: u32,
334    /// Logical operations and their containment.
335    pub nodes: Vec<ArchitectureNode>,
336    /// Directed logical data flow.
337    pub edges: Vec<ArchitectureEdge>,
338    /// Canonical parameter groups referenced by nodes in this graph.
339    pub parameter_groups: Vec<ArchitectureParameterGroup>,
340    /// Architecture-declared observation points, independent of execution support.
341    pub observations: ObservationCatalog,
342    /// Explicit omissions at this scope.
343    pub completeness: DescriptionCompleteness,
344}
345
346impl ArchitectureDescriptor {
347    /// Finds a node by its stable descriptor identity.
348    pub fn node(&self, id: &str) -> Option<&ArchitectureNode> {
349        self.nodes.iter().find(|node| node.id == id)
350    }
351}
352
353/// Shared declaration used by execution traversal and catalog generation.
354#[derive(Debug, Clone, Copy, Eq, PartialEq, Serialize, Deserialize)]
355#[serde(rename_all = "snake_case")]
356pub enum UnitObservation {
357    /// Input boundary of an execution unit.
358    Input,
359    /// Output boundary of an execution unit.
360    Output,
361}
362
363impl UnitObservation {
364    /// Formats the exact selector used by instrumentation and discovery.
365    pub fn path(self, unit: &str) -> String {
366        format!(
367            "{unit}.{}",
368            match self {
369                Self::Input => "input",
370                Self::Output => "output",
371            }
372        )
373    }
374}
375
376/// Fields of the runtime's normalized routing event, shared with host collectors.
377#[derive(Debug, Clone, Copy, Eq, PartialEq, Serialize, Deserialize)]
378#[serde(rename_all = "snake_case")]
379pub enum RoutingObservationField {
380    /// Selected expert identifiers.
381    SelectedExperts,
382    /// Selected scores before top-k renormalization.
383    SelectedScores,
384    /// Final coefficients applied to routed contributions.
385    Coefficients,
386    /// Combined routed-expert contribution.
387    RoutedOutput,
388    /// Rank-local routed contribution.
389    LocalRoutedOutput,
390    /// Routed contribution after collective reduction.
391    ReducedRoutedOutput,
392    /// Shared-expert contribution including any shared gate.
393    SharedOutput,
394    /// Combined routed and shared contribution.
395    CombinedOutput,
396}
397
398impl RoutingObservationField {
399    /// Formats the exact selector used by instrumentation and discovery.
400    pub fn path(self, module: &str) -> String {
401        let field = match self {
402            Self::SelectedExperts => "selected_experts",
403            Self::SelectedScores => "selected_scores",
404            Self::Coefficients => "coefficients",
405            Self::RoutedOutput => "routed_output",
406            Self::LocalRoutedOutput => "local_routed_output",
407            Self::ReducedRoutedOutput => "reduced_routed_output",
408            Self::SharedOutput => "shared_output",
409            Self::CombinedOutput => "combined_output",
410        };
411        format!("{module}.routing.{field}")
412    }
413}
414
415/// Tensor element category before native-to-host conversion.
416#[derive(Debug, Clone, Copy, Eq, PartialEq, Serialize, Deserialize)]
417#[serde(rename_all = "snake_case")]
418pub enum ObservationDtype {
419    /// Floating-point tensor; native precision is execution-dependent.
420    Floating,
421    /// Integer tensor; signedness is supplied by the captured host value.
422    Integer,
423    /// Boolean tensor.
424    Boolean,
425    /// Element category is not described.
426    Unknown,
427}
428
429/// Portable value category produced by an advertised observation.
430#[derive(Debug, Clone, Copy, Eq, PartialEq, Serialize, Deserialize)]
431#[serde(rename_all = "snake_case")]
432pub enum ObservationValueType {
433    /// A complete tensor, materialized through `TensorObservation`.
434    Tensor,
435}
436
437/// Capture timing relative to an intervention at the same path.
438#[derive(Debug, Clone, Copy, Eq, PartialEq, Serialize, Deserialize)]
439#[serde(rename_all = "snake_case")]
440pub enum ObservationPosition {
441    /// Value passed to observe, before replacement at this exact point.
442    BeforeIntervention,
443    /// A read-only event after dispatch; no replacement at this point.
444    ReadOnly,
445    /// Value after replacement at this point.
446    AfterIntervention,
447}
448
449/// Conditional mechanism or input required to emit a point.
450#[derive(Debug, Clone, Copy, Eq, PartialEq, Serialize, Deserialize)]
451#[serde(rename_all = "snake_case")]
452pub enum ObservationRequirement {
453    /// Requires activation capture through instrumented execution.
454    ActivationHooks,
455    /// Requires normalized routing-event capture.
456    RoutingEvents,
457    /// Requires the corresponding media input.
458    MediaInput,
459    /// Requires the corresponding prediction group to execute.
460    PredictionExecution,
461}
462
463/// One exact implemented tensor observation, not a hypothetical internal value.
464#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
465pub struct ObservationPoint {
466    /// Exact activation path accepted by ObservationSelector::Exact.
467    pub path: String,
468    /// Associated architecture node identity.
469    pub node_id: String,
470    /// Semantic meaning of the captured value.
471    pub meaning: String,
472    /// Portable value category.
473    pub value_type: ObservationValueType,
474    /// Semantic dtype before backend-specific host conversion.
475    pub dtype: ObservationDtype,
476    /// Tensor axes in storage order, when known.
477    pub axes: Option<Vec<TensorAxis>>,
478    /// Availability during a prefill operation.
479    pub prefill: bool,
480    /// Availability during a cached decode operation.
481    pub decode: bool,
482    /// Conditions in addition to phase availability.
483    pub requirements: Vec<ObservationRequirement>,
484    /// Position relative to an intervention at this exact point.
485    pub position: ObservationPosition,
486    /// Full tensor retained until host materialization; bytes depend on runtime shape/dtype.
487    pub retained_bytes: Option<u64>,
488    /// Host materialization bytes when reliably known; otherwise unknown.
489    pub host_bytes: Option<u64>,
490}
491
492/// Versioned set of implemented architecture-declared capture points.
493#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
494pub struct ObservationCatalog {
495    /// Version of the discovery wire schema.
496    pub schema_version: u32,
497    /// Observation points in deterministic path order.
498    pub points: Vec<ObservationPoint>,
499    /// Explicit omissions at this scope.
500    pub completeness: DescriptionCompleteness,
501}
502
503impl ObservationCatalog {
504    /// Finds an implemented observation by its exact selectable path.
505    pub fn get(&self, path: &str) -> Option<&ObservationPoint> {
506        self.points.iter().find(|point| point.path == path)
507    }
508}
509
510/// Side-effect-free host collector facts. Unknown is the conservative default.
511#[derive(Debug, Clone, Copy, Default, Eq, PartialEq, Serialize, Deserialize)]
512pub struct ObservationMechanisms {
513    /// The backend collects ordinary activation tensors.
514    pub activation_tensors: bool,
515    /// The backend collects normalized routing-event tensors.
516    pub routing_tensors: bool,
517    /// Floating observations are converted to portable F32 host values.
518    pub floating_to_f32: bool,
519}
520
521/// Phase-specific support under one selected execution configuration.
522#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
523#[serde(tag = "status", content = "reason", rename_all = "snake_case")]
524pub enum ObservationSupportStatus {
525    /// The selected execution emits this point in this phase.
526    Supported,
527    /// Supported if the stated input or execution condition holds.
528    Conditional(String),
529    /// The selected execution cannot emit this observation.
530    Unsupported(String),
531    /// Available facts do not prove capture support.
532    Unverified(String),
533}
534
535/// Support for one architecture-declared path under an exact selected execution.
536#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
537pub struct ObservationSupport {
538    /// Exact activation path accepted by ObservationSelector::Exact.
539    pub path: String,
540    /// Availability during a prefill operation.
541    pub prefill: ObservationSupportStatus,
542    /// Availability during a cached decode operation.
543    pub decode: ObservationSupportStatus,
544    /// Host conversion fact; integer signedness is reported by the captured value.
545    pub floating_to_f32: bool,
546}
547
548/// Versioned per-path support facts, separate from the logical architecture.
549#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
550pub struct ObservationSupportReport {
551    /// Version of the discovery wire schema.
552    pub schema_version: u32,
553    /// Observation points in deterministic path order.
554    pub points: Vec<ObservationSupport>,
555    /// Bounded transformation mechanisms and their execution/storage conditions.
556    #[serde(default)]
557    pub capture: crate::capture::CaptureCapabilities,
558}