Skip to main content

eredu_nn/
lib.rs

1//! Backend-neutral neural computation contracts.
2//!
3//! Architecture implementations use opaque backend tensors through this
4//! interface. Tensor storage, physical layout, laziness, device placement, and
5//! synchronization remain owned by the selected backend.
6
7#![warn(missing_docs)]
8
9extern crate self as eredu_nn;
10
11use std::fmt::Debug;
12
13use eredu_checkpoint::LinearFormat;
14
15pub use eredu_nn_macros::Parameterized;
16
17/// Reusable patch projection and multi-axis position operations.
18pub mod multimodal;
19/// Pure sequence layouts shared by patch-based encoders.
20pub mod sequence_layout;
21
22/// Backend operation failure.
23#[derive(Debug, Clone, thiserror::Error)]
24#[error("{message}")]
25pub struct Error {
26    message: String,
27}
28
29impl Error {
30    /// Creates a backend operation failure without exposing backend-native
31    /// exception types through architecture code.
32    pub fn backend(error: impl std::fmt::Display) -> Self {
33        Self {
34            message: error.to_string(),
35        }
36    }
37}
38
39/// One axis of a backend-neutral tensor view.
40#[derive(Debug, Clone, Copy, Eq, PartialEq)]
41pub enum Index {
42    /// Retains the complete axis.
43    Full,
44    /// Selects one element and removes the axis.
45    At(i32),
46    /// Retains the half-open interval `[start, end)`.
47    Range(i32, i32),
48}
49
50/// Padding behavior for convolutional architecture components.
51#[derive(Debug, Clone, Copy, Eq, PartialEq)]
52pub enum PadMode {
53    /// Pads with zeroes.
54    Constant,
55    /// Repeats the nearest edge value.
56    Edge,
57}
58
59/// Attention masking selected by architecture code.
60#[derive(Debug, Clone, Copy)]
61pub enum AttentionMask<'a, T> {
62    /// No attention mask.
63    None,
64    /// Standard causal mask.
65    Causal,
66    /// Backend tensor added to attention logits.
67    Tensor(&'a T),
68}
69
70/// Validated expansion from a smaller logical head axis to a repeated head
71/// axis. Repetition preserves grouped order: each source head is repeated
72/// contiguously `target_heads / source_heads` times.
73#[derive(Debug, Clone, Copy, Eq, PartialEq)]
74pub struct HeadExpansion {
75    /// Tensor axis containing source heads.
76    pub axis: usize,
77    /// Number of source heads.
78    pub source_heads: i32,
79    /// Number of heads after expansion.
80    pub target_heads: i32,
81}
82
83impl HeadExpansion {
84    /// Validates the head counts and the selected tensor axis.
85    pub fn validate<T: Tensor>(&self, input: &T) -> Result<(), Error> {
86        let shape = input.shape();
87        if self.source_heads <= 0
88            || self.target_heads <= 0
89            || self.target_heads % self.source_heads != 0
90            || shape.get(self.axis).copied() != Some(self.source_heads)
91        {
92            return Err(Error::backend(format!(
93                "invalid head expansion axis={} source={} target={} shape={shape:?}",
94                self.axis, self.source_heads, self.target_heads
95            )));
96        }
97        Ok(())
98    }
99
100    /// Number of adjacent copies of each source head.
101    pub const fn repeats(self) -> i32 {
102        self.target_heads / self.source_heads
103    }
104}
105
106/// One unmasked attention request over validated contiguous sequence segments.
107#[derive(Debug, Clone, Copy)]
108pub struct SegmentedAttentionInput<'a, T> {
109    /// Queries shaped `[tokens, heads, dimensions]`.
110    pub queries: &'a T,
111    /// Keys shaped `[tokens, heads, dimensions]`.
112    pub keys: &'a T,
113    /// Values shaped `[tokens, heads, value_dimensions]`.
114    pub values: &'a T,
115    /// Positive contiguous segment lengths whose sum equals `tokens`.
116    pub segment_lengths: &'a [i32],
117    /// Query/key score multiplier.
118    pub scale: f32,
119}
120
121impl<T: Tensor> SegmentedAttentionInput<'_, T> {
122    /// Validates tensor and segment geometry without inspecting tensor values.
123    pub fn validate(&self) -> Result<(), Error> {
124        let query = self.queries.shape();
125        let key = self.keys.shape();
126        let value = self.values.shape();
127        if query.len() != 3
128            || key.len() != 3
129            || value.len() != 3
130            || query[0] <= 0
131            || query[1] <= 0
132            || query[2] <= 0
133            || query[0] != key[0]
134            || query[0] != value[0]
135            || query[1] != key[1]
136            || query[1] != value[1]
137            || query[2] != key[2]
138            || value[2] <= 0
139            || !self.scale.is_finite()
140            || self.scale <= 0.0
141        {
142            return Err(Error::backend(format!(
143                "invalid segmented attention geometry q={query:?} k={key:?} v={value:?} scale={}",
144                self.scale
145            )));
146        }
147        validate_segment_lengths(query[0], self.segment_lengths)
148    }
149}
150
151/// Validates positive contiguous segment lengths and their exact total.
152pub fn validate_segment_lengths(total: i32, segment_lengths: &[i32]) -> Result<(), Error> {
153    if total <= 0 || segment_lengths.is_empty() {
154        return Err(Error::backend(format!(
155            "segmented attention requires a positive total and at least one segment, got total={total} segments={segment_lengths:?}"
156        )));
157    }
158    let mut sum = 0i32;
159    for &length in segment_lengths {
160        if length <= 0 {
161            return Err(Error::backend(format!(
162                "segmented attention lengths must be positive, got {segment_lengths:?}"
163            )));
164        }
165        sum = sum.checked_add(length).ok_or_else(|| {
166            Error::backend("segmented attention length total overflowed signed 32-bit geometry")
167        })?;
168        if sum > total {
169            return Err(Error::backend(format!(
170                "segmented attention lengths exceed total {total}: {segment_lengths:?}"
171            )));
172        }
173    }
174    if sum != total {
175        return Err(Error::backend(format!(
176            "segmented attention lengths sum to {sum}, expected {total}"
177        )));
178    }
179    Ok(())
180}
181
182/// Deterministic host reference for grouped head repetition.
183pub fn reference_expand_heads(
184    values: &[f32],
185    shape: &[usize],
186    axis: usize,
187    target_heads: usize,
188) -> Result<(Vec<f32>, Vec<usize>), Error> {
189    let source_heads = shape.get(axis).copied().unwrap_or(0);
190    if source_heads == 0 || target_heads == 0 || !target_heads.is_multiple_of(source_heads) {
191        return Err(Error::backend(format!(
192            "invalid reference head expansion axis={axis} target={target_heads} shape={shape:?}"
193        )));
194    }
195    let elements = shape.iter().try_fold(1usize, |total, width| {
196        total
197            .checked_mul(*width)
198            .ok_or_else(|| Error::backend("reference head expansion element count overflowed"))
199    })?;
200    if elements != values.len() {
201        return Err(Error::backend(format!(
202            "reference head expansion expected {elements} values, got {}",
203            values.len()
204        )));
205    }
206    let outer = shape[..axis].iter().product::<usize>();
207    let inner = shape[axis + 1..].iter().product::<usize>();
208    let repeats = target_heads / source_heads;
209    let mut output = Vec::with_capacity(outer * target_heads * inner);
210    for outer_index in 0..outer {
211        for source in 0..source_heads {
212            let start = (outer_index * source_heads + source) * inner;
213            for _ in 0..repeats {
214                output.extend_from_slice(&values[start..start + inner]);
215            }
216        }
217    }
218    let mut output_shape = shape.to_vec();
219    output_shape[axis] = target_heads;
220    Ok((output, output_shape))
221}
222
223/// Deterministic host reference for unmasked segmented scaled-dot-product
224/// attention. Inputs and output use token-major `[tokens, heads, dimensions]`
225/// storage.
226#[allow(clippy::too_many_arguments)]
227pub fn reference_segmented_attention(
228    tokens: usize,
229    heads: usize,
230    dimensions: usize,
231    value_dimensions: usize,
232    queries: &[f32],
233    keys: &[f32],
234    values: &[f32],
235    segment_lengths: &[i32],
236    scale: f32,
237) -> Result<Vec<f32>, Error> {
238    let tokens_i32 = i32::try_from(tokens)
239        .map_err(|_| Error::backend("reference segmented attention token count exceeds i32"))?;
240    validate_segment_lengths(tokens_i32, segment_lengths)?;
241    if heads == 0
242        || dimensions == 0
243        || value_dimensions == 0
244        || !scale.is_finite()
245        || scale <= 0.0
246        || queries.len() != tokens * heads * dimensions
247        || keys.len() != tokens * heads * dimensions
248        || values.len() != tokens * heads * value_dimensions
249    {
250        return Err(Error::backend(
251            "invalid reference segmented attention geometry",
252        ));
253    }
254    let mut output = vec![0.0f32; tokens * heads * value_dimensions];
255    let mut segment_start = 0usize;
256    for &length in segment_lengths {
257        let length = usize::try_from(length).expect("validated positive segment length");
258        let segment_end = segment_start + length;
259        for query_token in segment_start..segment_end {
260            for head in 0..heads {
261                let mut scores = Vec::with_capacity(length);
262                for key_token in segment_start..segment_end {
263                    let mut score = 0.0f32;
264                    for dimension in 0..dimensions {
265                        let query_index = (query_token * heads + head) * dimensions + dimension;
266                        let key_index = (key_token * heads + head) * dimensions + dimension;
267                        score += queries[query_index] * keys[key_index];
268                    }
269                    scores.push(score * scale);
270                }
271                let maximum = scores.iter().copied().fold(f32::NEG_INFINITY, f32::max);
272                let denominator = scores
273                    .iter_mut()
274                    .map(|score| {
275                        *score = (*score - maximum).exp();
276                        *score
277                    })
278                    .sum::<f32>();
279                for value_dimension in 0..value_dimensions {
280                    let mut result = 0.0f32;
281                    for (relative, key_token) in (segment_start..segment_end).enumerate() {
282                        let value_index =
283                            (key_token * heads + head) * value_dimensions + value_dimension;
284                        result += scores[relative] / denominator * values[value_index];
285                    }
286                    let output_index =
287                        (query_token * heads + head) * value_dimensions + value_dimension;
288                    output[output_index] = result;
289                }
290            }
291        }
292        segment_start = segment_end;
293    }
294    Ok(output)
295}
296
297/// Value source for an attention unit that owns key/value state.
298#[derive(Debug, Clone, Copy, Eq, Hash, PartialEq)]
299pub enum AttentionValueSource {
300    /// Values are produced by an independent projection.
301    Projected,
302    /// Projected keys are reused as values.
303    ReuseKey,
304}
305
306/// Typed source and publication policy for attention key/value state.
307#[derive(Debug, Clone, Copy, Eq, Hash, PartialEq)]
308pub enum AttentionStateSource {
309    /// Own projections and retain state only for this attention unit.
310    Local {
311        /// Value projection topology.
312        value: AttentionValueSource,
313    },
314    /// Own projections and publish state for later consumers.
315    Publish {
316        /// Value projection topology.
317        value: AttentionValueSource,
318    },
319    /// Consume state published by another unit or supplied externally.
320    Shared,
321}
322
323impl AttentionStateSource {
324    /// Returns whether the attention unit owns projections and mutable state.
325    pub const fn owns_state(self) -> bool {
326        !matches!(self, Self::Shared)
327    }
328
329    /// Returns whether the resulting state must be published.
330    pub const fn publishes_state(self) -> bool {
331        matches!(self, Self::Publish { .. })
332    }
333
334    /// Returns the value source for a state-owning unit.
335    pub const fn value(self) -> Option<AttentionValueSource> {
336        match self {
337            Self::Local { value } | Self::Publish { value } => Some(value),
338            Self::Shared => None,
339        }
340    }
341}
342
343#[cfg(test)]
344mod attention_state_source_tests {
345    use super::{AttentionStateSource, AttentionValueSource};
346
347    #[test]
348    fn ownership_publication_and_key_as_value_are_independent() {
349        let local = AttentionStateSource::Local {
350            value: AttentionValueSource::Projected,
351        };
352        let publisher = AttentionStateSource::Publish {
353            value: AttentionValueSource::ReuseKey,
354        };
355        assert!(local.owns_state());
356        assert!(!local.publishes_state());
357        assert_eq!(local.value(), Some(AttentionValueSource::Projected));
358        assert!(publisher.owns_state());
359        assert!(publisher.publishes_state());
360        assert_eq!(publisher.value(), Some(AttentionValueSource::ReuseKey));
361        assert!(!AttentionStateSource::Shared.owns_state());
362        assert_eq!(AttentionStateSource::Shared.value(), None);
363    }
364}
365
366#[cfg(test)]
367mod recurrent_encoder_contract_tests {
368    use super::{
369        reference_expand_heads, reference_segmented_attention, validate_segment_lengths,
370        NormalizationConstructionSpec, NormalizationScale,
371    };
372
373    #[test]
374    fn normalization_construction_rejects_invalid_geometry_and_scalars() {
375        assert!(NormalizationConstructionSpec {
376            dimensions: 8,
377            epsilon: 1e-6,
378            scale: NormalizationScale::Unit,
379        }
380        .validate()
381        .is_ok());
382        assert!(NormalizationConstructionSpec {
383            dimensions: 0,
384            epsilon: 1e-6,
385            scale: NormalizationScale::Unit,
386        }
387        .validate()
388        .is_err());
389        assert!(NormalizationConstructionSpec {
390            dimensions: 8,
391            epsilon: f32::NAN,
392            scale: NormalizationScale::Unit,
393        }
394        .validate()
395        .is_err());
396    }
397
398    #[test]
399    fn head_expansion_reference_preserves_grouped_row_order() {
400        let (values, shape) =
401            reference_expand_heads(&[1.0, 2.0, 3.0, 4.0], &[1, 2, 2], 1, 4).unwrap();
402        assert_eq!(shape, vec![1, 4, 2]);
403        assert_eq!(values, vec![1.0, 2.0, 1.0, 2.0, 3.0, 4.0, 3.0, 4.0]);
404        assert!(reference_expand_heads(&[1.0, 2.0], &[1, 2], 1, 3).is_err());
405    }
406
407    #[test]
408    fn segmented_attention_reference_is_independent_per_contiguous_segment() {
409        let output = reference_segmented_attention(
410            3,
411            1,
412            1,
413            1,
414            &[0.0, 0.0, 0.0],
415            &[0.0, 0.0, 0.0],
416            &[2.0, 4.0, 9.0],
417            &[2, 1],
418            1.0,
419        )
420        .unwrap();
421        assert_eq!(output, vec![3.0, 3.0, 9.0]);
422        assert!(validate_segment_lengths(3, &[]).is_err());
423        assert!(validate_segment_lengths(3, &[2, 0, 1]).is_err());
424        assert!(validate_segment_lengths(3, &[2]).is_err());
425        assert!(validate_segment_lengths(3, &[2, 2]).is_err());
426        assert!(validate_segment_lengths(i32::MAX, &[i32::MAX, 1]).is_err());
427    }
428}
429
430/// Typed sparse/indexed attention request.
431///
432/// Architecture code owns how positions are selected, causal eligibility,
433/// compression ratios, top-k, and sink policy. A backend may fuse gathering,
434/// shared-softmax attention, and value reduction without exposing an
435/// accelerator-specific indexing API.
436#[derive(Debug, Clone, Copy)]
437pub struct IndexedAttentionInput<'a, T> {
438    /// Queries shaped `[batch, heads, query_tokens, key_dimensions]`.
439    pub queries: &'a T,
440    /// Bounded local keys shaped `[batch, local_tokens, key_dimensions]`.
441    pub local_keys: &'a T,
442    /// Bounded local values shaped `[batch, local_tokens, value_dimensions]`.
443    pub local_values: &'a T,
444    /// Compressed/indexable keys shaped `[batch, pooled_tokens, key_dimensions]`.
445    pub pooled_keys: &'a T,
446    /// Compressed/indexable values shaped `[batch, pooled_tokens, value_dimensions]`.
447    pub pooled_values: &'a T,
448    /// Selected pooled positions shaped `[batch, query_tokens, selected]`.
449    pub selected_positions: &'a T,
450    /// Query/key score multiplier.
451    pub scale: f32,
452    /// Optional mask broadcastable to local scores.
453    pub local_mask: Option<&'a T>,
454    /// Optional mask broadcastable to selected pooled scores.
455    pub pooled_mask: Option<&'a T>,
456    /// Optional learned per-head sink logits.
457    pub sinks: Option<&'a T>,
458}
459
460/// Dense attention over bounded local keys plus complete pooled history.
461#[derive(Debug, Clone, Copy)]
462pub struct PooledAttentionInput<'a, T> {
463    /// Queries shaped `[batch, heads, query_tokens, dimensions]`.
464    pub queries: &'a T,
465    /// Bounded local keys and values shaped `[batch, local_tokens, dimensions]`.
466    pub local: &'a T,
467    /// Complete pooled keys and values shaped `[batch, pooled_tokens, dimensions]`.
468    pub pooled: &'a T,
469    /// Query/key score multiplier.
470    pub scale: f32,
471    /// Optional mask broadcastable to local scores.
472    pub local_mask: Option<&'a T>,
473    /// Optional mask broadcastable to pooled scores.
474    pub pooled_mask: Option<&'a T>,
475    /// Optional learned per-head sink logits.
476    pub sinks: Option<&'a T>,
477}
478
479/// Architecture-selected pooled-position scoring request.
480///
481/// Backends may fuse score construction, causal masking, and top-k selection
482/// without exposing device-specific partition or gather operations.
483#[derive(Debug, Clone, Copy)]
484pub struct PooledPositionInput<'a, T> {
485    /// Rotary queries shaped `[batch, heads, query_tokens, dimensions]`.
486    pub queries: &'a T,
487    /// Compressed index keys shaped `[batch, pooled_tokens, dimensions]`.
488    pub pooled_keys: &'a T,
489    /// Per-token head weights shaped `[batch, query_tokens, heads]`.
490    pub head_weights: &'a T,
491    /// Optional eligibility mask shaped `[query_tokens, pooled_tokens]` or a
492    /// broadcast-compatible batch variant.
493    pub mask: Option<&'a T>,
494    /// Number of pooled positions selected per query token.
495    pub top_k: i32,
496    /// Score multiplier applied after nonnegative clamping.
497    pub scale: f32,
498    /// Head-weight multiplier applied before reducing the head axis.
499    pub head_scale: f32,
500}
501
502/// Causal attention with a learned relative-position profile.
503///
504/// Architecture code projects per-token relative features into `profiles`;
505/// the backend owns position-index gathering and score materialization so no
506/// device values cross the host boundary.
507#[derive(Debug, Clone, Copy)]
508pub struct RelativeAttentionInput<'a, T> {
509    /// Normalized queries shaped `[batch, heads, query_tokens, dimensions]`.
510    pub queries: &'a T,
511    /// Normalized keys shaped `[batch, kv_heads, key_tokens, dimensions]`.
512    pub keys: &'a T,
513    /// Values shaped `[batch, kv_heads, key_tokens, dimensions]`.
514    pub values: &'a T,
515    /// Learned profiles shaped `[batch, heads, query_tokens, relative_extent]`.
516    pub profiles: &'a T,
517    /// Absolute position of the first query token.
518    pub query_offset: i32,
519    /// Absolute position of the first retained key token.
520    pub key_offset: i32,
521    /// Optional causal sliding-window width.
522    pub window: Option<i32>,
523    /// Optional position floor for logarithmic global-attention scaling.
524    pub log_scaling_floor: Option<i32>,
525    /// Multiplier applied to the logarithmic scale above the floor.
526    pub log_scaling_alpha: f32,
527}
528
529impl<T: Tensor> RelativeAttentionInput<'_, T> {
530    /// Validates exact head, sequence, and relative-profile geometry.
531    pub fn validate(&self) -> Result<(), Error> {
532        let query = self.queries.shape();
533        let key = self.keys.shape();
534        let value = self.values.shape();
535        let profiles = self.profiles.shape();
536        if query.len() != 4
537            || key.len() != 4
538            || value.len() != 4
539            || profiles.len() != 4
540            || query[0] != key[0]
541            || key != value
542            || query[2] != profiles[2]
543            || query[0] != profiles[0]
544            || query[1] != profiles[1]
545            || query[3] != key[3]
546            || query[1] % key[1] != 0
547            || profiles[3] <= 0
548            || self.window.is_some_and(|window| window <= 0)
549            || self.log_scaling_floor.is_some_and(|floor| floor <= 0)
550            || !self.log_scaling_alpha.is_finite()
551        {
552            return Err(Error::backend(format!(
553                "invalid relative attention geometry q={query:?} k={key:?} v={value:?} profiles={profiles:?} window={:?} floor={:?} alpha={}",
554                self.window, self.log_scaling_floor, self.log_scaling_alpha
555            )));
556        }
557        Ok(())
558    }
559}
560
561impl<T: Tensor> IndexedAttentionInput<'_, T> {
562    /// Validates semantic ranks and exact non-broadcast geometry without
563    /// materializing any backend values.
564    pub fn validate(&self) -> Result<(), Error> {
565        let query = self.queries.shape();
566        let local_keys = self.local_keys.shape();
567        let local_values = self.local_values.shape();
568        let pooled_keys = self.pooled_keys.shape();
569        let pooled_values = self.pooled_values.shape();
570        let selected = self.selected_positions.shape();
571        if query.len() != 4
572            || local_keys.len() != 3
573            || local_values.len() != 3
574            || pooled_keys.len() != 3
575            || pooled_values.len() != 3
576            || selected.len() != 3
577            || query[0] != local_keys[0]
578            || query[0] != local_values[0]
579            || query[0] != pooled_keys[0]
580            || query[0] != pooled_values[0]
581            || query[0] != selected[0]
582            || query[2] != selected[1]
583            || query[3] != local_keys[2]
584            || query[3] != pooled_keys[2]
585            || local_keys[1] != local_values[1]
586            || pooled_keys[1] != pooled_values[1]
587            || local_values[2] != pooled_values[2]
588            || selected[2] <= 0
589            || pooled_keys[1] <= 0
590        {
591            return Err(Error::backend(format!(
592                "invalid indexed-attention geometry: queries={query:?} local_keys={local_keys:?} local_values={local_values:?} pooled_keys={pooled_keys:?} pooled_values={pooled_values:?} selected={selected:?}"
593            )));
594        }
595        if !self.scale.is_finite() || self.scale <= 0.0 {
596            return Err(Error::backend(format!(
597                "indexed-attention scale must be finite and positive, got {}",
598                self.scale
599            )));
600        }
601        if let Some(sinks) = self.sinks {
602            if sinks.shape() != [query[1]] {
603                return Err(Error::backend(format!(
604                    "indexed-attention sinks require shape [{}], got {:?}",
605                    query[1],
606                    sinks.shape()
607                )));
608            }
609        }
610        Ok(())
611    }
612}
613
614/// Stable authoritative identity of one logical model parameter.
615#[derive(Debug, Clone, Eq, Hash, Ord, PartialEq, PartialOrd)]
616pub struct ParameterId(String);
617
618impl ParameterId {
619    /// Creates a non-empty parameter identity.
620    pub fn new(id: impl Into<String>) -> Result<Self, ParameterTopologyError> {
621        let id = id.into();
622        if id.trim().is_empty() {
623            return Err(ParameterTopologyError::EmptyId);
624        }
625        Ok(Self(id))
626    }
627
628    /// Returns the stable checkpoint-facing identity.
629    pub fn as_str(&self) -> &str {
630        &self.0
631    }
632}
633
634impl std::fmt::Display for ParameterId {
635    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
636        formatter.write_str(&self.0)
637    }
638}
639
640/// Complete logical declaration for one parameter slot.
641#[derive(Debug, Clone, Eq, PartialEq)]
642pub struct ParameterSpec {
643    /// Stable authoritative identity.
644    pub id: ParameterId,
645    /// Whether optimizers may update this parameter by default.
646    pub trainable: bool,
647    /// Authoritative destination when this slot aliases a tied parameter.
648    pub alias_of: Option<ParameterId>,
649    /// Optional atomic encoding or sharding group.
650    pub group: Option<String>,
651    /// Semantic role within an encoded linear parameter group.
652    pub linear_companion: Option<LinearCompanionRole>,
653    /// Primary linear weight owned by this physical companion.
654    pub linear_companion_of: Option<ParameterId>,
655}
656
657impl ParameterSpec {
658    /// Declares an ordinary trainable parameter.
659    pub fn trainable(id: impl Into<String>) -> Result<Self, ParameterTopologyError> {
660        Ok(Self {
661            id: ParameterId::new(id)?,
662            trainable: true,
663            alias_of: None,
664            group: None,
665            linear_companion: None,
666            linear_companion_of: None,
667        })
668    }
669}
670
671/// Semantic role of a physical companion in an encoded linear parameter.
672#[derive(Debug, Clone, Copy, Eq, Ord, PartialEq, PartialOrd)]
673pub enum LinearCompanionRole {
674    /// Per-group or per-block scale tensor.
675    Scale,
676    /// Per-group affine zero-point/bias tensor.
677    AffineBias,
678}
679
680/// Parameter metadata observed during traversal.
681#[derive(Debug, Clone, Eq, PartialEq)]
682pub struct ParameterMetadata {
683    /// Stable authoritative identity.
684    pub id: ParameterId,
685    /// Current trainable state.
686    pub trainable: bool,
687    /// Authoritative destination for a tied alias.
688    pub alias_of: Option<ParameterId>,
689    /// Optional atomic parameter group.
690    pub group: Option<String>,
691    /// Semantic role within an encoded linear parameter group.
692    pub linear_companion: Option<LinearCompanionRole>,
693    /// Primary linear weight owned by this physical companion.
694    pub linear_companion_of: Option<ParameterId>,
695}
696
697impl ParameterMetadata {
698    /// Creates traversal metadata from a construction specification.
699    pub fn from_spec(spec: &ParameterSpec, trainable: bool) -> Self {
700        Self {
701            id: spec.id.clone(),
702            trainable,
703            alias_of: spec.alias_of.clone(),
704            group: spec.group.clone(),
705            linear_companion: spec.linear_companion,
706            linear_companion_of: spec.linear_companion_of.clone(),
707        }
708    }
709}
710
711/// Invalid stable parameter topology.
712#[derive(Debug, Clone, Eq, PartialEq, thiserror::Error)]
713pub enum ParameterTopologyError {
714    /// A parameter identity is empty.
715    #[error("parameter identity must not be empty")]
716    EmptyId,
717    /// The same stable identity was visited more than once.
718    #[error("parameter identity {0} is duplicated")]
719    DuplicateId(ParameterId),
720    /// A tied alias points to an identity that is not in the topology.
721    #[error("parameter alias {alias} points to missing destination {destination}")]
722    MissingAliasDestination {
723        /// Identity of the aliasing slot.
724        alias: ParameterId,
725        /// Missing authoritative destination.
726        destination: ParameterId,
727    },
728    /// A tied alias points to another alias instead of an authoritative slot.
729    #[error("parameter alias {alias} points to non-authoritative alias {destination}")]
730    AliasTargetsAlias {
731        /// Identity of the aliasing slot.
732        alias: ParameterId,
733        /// Non-authoritative destination.
734        destination: ParameterId,
735    },
736}
737
738/// Immutable statically dispatched parameter visitor.
739pub trait ParameterVisitor<'a, T: 'a> {
740    /// Visits one authoritative parameter slot.
741    fn visit(&mut self, metadata: ParameterMetadata, value: &'a T);
742}
743
744/// Mutable statically dispatched parameter visitor.
745pub trait ParameterVisitorMut<'a, T: 'a> {
746    /// Visits one authoritative mutable parameter slot.
747    fn visit_mut(&mut self, metadata: ParameterMetadata, value: &'a mut T);
748}
749
750/// Backend-neutral parameter topology for a module or operator.
751pub trait Parameterized<T: 'static> {
752    /// Visits every parameter exactly once using stable identities.
753    fn visit_parameters<'a, V>(&'a self, visitor: &mut V)
754    where
755        V: ParameterVisitor<'a, T>;
756
757    /// Mutably visits every parameter exactly once using stable identities.
758    fn visit_parameters_mut<'a, V>(&'a mut self, visitor: &mut V)
759    where
760        V: ParameterVisitorMut<'a, T>;
761
762    /// Updates whether all parameters in this module are trainable.
763    fn set_trainable(&mut self, trainable: bool);
764}
765
766/// Collects and validates the stable parameter topology exposed by a module.
767pub fn validate_parameter_topology<T: 'static, M>(
768    module: &M,
769) -> Result<Vec<ParameterMetadata>, ParameterTopologyError>
770where
771    M: Parameterized<T>,
772{
773    struct Collector(Vec<ParameterMetadata>);
774    impl<'a, T: 'a> ParameterVisitor<'a, T> for Collector {
775        fn visit(&mut self, metadata: ParameterMetadata, _value: &'a T) {
776            self.0.push(metadata);
777        }
778    }
779
780    let mut collector = Collector(Vec::new());
781    module.visit_parameters(&mut collector);
782    let mut topology = std::collections::BTreeMap::new();
783    for metadata in &collector.0 {
784        if topology.insert(metadata.id.clone(), metadata).is_some() {
785            return Err(ParameterTopologyError::DuplicateId(metadata.id.clone()));
786        }
787    }
788    for metadata in &collector.0 {
789        let Some(destination) = &metadata.alias_of else {
790            continue;
791        };
792        let Some(target) = topology.get(destination) else {
793            return Err(ParameterTopologyError::MissingAliasDestination {
794                alias: metadata.id.clone(),
795                destination: destination.clone(),
796            });
797        };
798        if target.alias_of.is_some() {
799            return Err(ParameterTopologyError::AliasTargetsAlias {
800                alias: metadata.id.clone(),
801                destination: destination.clone(),
802            });
803        }
804    }
805    Ok(collector.0)
806}
807
808/// Shape and checkpoint identity for an affine projection.
809#[derive(Debug, Clone)]
810pub struct LinearSpec {
811    /// Input feature count.
812    pub input: i32,
813    /// Output feature count.
814    pub output: i32,
815    /// Stable weight slot.
816    pub weight: ParameterSpec,
817    /// Optional stable bias slot.
818    pub bias: Option<ParameterSpec>,
819    /// Complete physical checkpoint encoding and exact companion identities.
820    pub format: LinearFormatSpec,
821}
822
823/// Complete construction specification for a token embedding table.
824#[derive(Debug, Clone)]
825pub struct EmbeddingSpec {
826    /// Vocabulary row count.
827    pub vocabulary: i32,
828    /// Embedding width.
829    pub dimensions: i32,
830    /// Stable embedding weight slot.
831    pub weight: ParameterSpec,
832    /// Complete physical checkpoint encoding and exact companion identities.
833    pub format: LinearFormatSpec,
834}
835
836/// Architecture-owned physical encoding and exact companion parameters.
837///
838/// Backends consume these identities literally. They must never derive scale
839/// or affine-bias names from the primary weight identity.
840#[derive(Debug, Clone, Eq, PartialEq)]
841pub struct LinearFormatSpec {
842    format: LinearFormat,
843    scale: Option<ParameterSpec>,
844    affine_bias: Option<ParameterSpec>,
845}
846
847impl LinearFormatSpec {
848    /// Declares a dense or checkpoint-native encoding with no companions.
849    pub fn unscaled(format: LinearFormat) -> Result<Self, Error> {
850        let spec = Self {
851            format,
852            scale: None,
853            affine_bias: None,
854        };
855        spec.validate()?;
856        Ok(spec)
857    }
858
859    /// Declares an encoding with one exact scale companion.
860    pub fn scaled(format: LinearFormat, scale: ParameterSpec) -> Result<Self, Error> {
861        let mut scale = scale;
862        scale.linear_companion = Some(LinearCompanionRole::Scale);
863        scale.linear_companion_of = None;
864        let spec = Self {
865            format,
866            scale: Some(scale),
867            affine_bias: None,
868        };
869        spec.validate()?;
870        Ok(spec)
871    }
872
873    /// Declares an affine encoding with exact scale and bias companions.
874    pub fn affine(
875        format: LinearFormat,
876        scale: ParameterSpec,
877        affine_bias: ParameterSpec,
878    ) -> Result<Self, Error> {
879        let mut scale = scale;
880        scale.linear_companion = Some(LinearCompanionRole::Scale);
881        scale.linear_companion_of = None;
882        let mut affine_bias = affine_bias;
883        affine_bias.linear_companion = Some(LinearCompanionRole::AffineBias);
884        affine_bias.linear_companion_of = None;
885        let spec = Self {
886            format,
887            scale: Some(scale),
888            affine_bias: Some(affine_bias),
889        };
890        spec.validate()?;
891        Ok(spec)
892    }
893
894    /// Physical tensor encoding.
895    pub const fn encoding(&self) -> LinearFormat {
896        self.format
897    }
898
899    /// Exact scale companion, when stored separately.
900    pub const fn scale(&self) -> Option<&ParameterSpec> {
901        self.scale.as_ref()
902    }
903
904    /// Exact affine-bias companion, when stored separately.
905    pub const fn affine_bias(&self) -> Option<&ParameterSpec> {
906        self.affine_bias.as_ref()
907    }
908
909    /// Validates that companion cardinality matches the physical encoding.
910    pub fn validate(&self) -> Result<(), Error> {
911        self.format.validate().map_err(Error::backend)?;
912        let expected = match self.format {
913            LinearFormat::Dense | LinearFormat::GgufIQuant { .. } => (false, false),
914            LinearFormat::MxFp4 | LinearFormat::E4M3BlockFp8(_) => (true, false),
915            LinearFormat::Affine(_) => (true, true),
916        };
917        if (self.scale.is_some(), self.affine_bias.is_some()) != expected {
918            return Err(Error::backend(format!(
919                "linear format {:?} requires scale/bias companions {:?}, got {:?}",
920                self.format,
921                expected,
922                (self.scale.is_some(), self.affine_bias.is_some())
923            )));
924        }
925        if self
926            .scale
927            .as_ref()
928            .zip(self.affine_bias.as_ref())
929            .is_some_and(|(scale, bias)| scale.id == bias.id)
930        {
931            return Err(Error::backend(
932                "linear scale and affine-bias companions require distinct identities",
933            ));
934        }
935        if self
936            .scale
937            .as_ref()
938            .is_some_and(|scale| scale.linear_companion != Some(LinearCompanionRole::Scale))
939            || self
940                .affine_bias
941                .as_ref()
942                .is_some_and(|bias| bias.linear_companion != Some(LinearCompanionRole::AffineBias))
943        {
944            return Err(Error::backend(
945                "linear format companions have invalid semantic roles",
946            ));
947        }
948        Ok(())
949    }
950
951    /// Validates companions against the primary weight identity.
952    pub fn validate_for_weight(&self, weight: &ParameterSpec) -> Result<(), Error> {
953        self.validate()?;
954        if self
955            .scale
956            .as_ref()
957            .into_iter()
958            .chain(self.affine_bias.as_ref())
959            .any(|companion| companion.id == weight.id)
960        {
961            return Err(Error::backend(format!(
962                "linear format companion reuses primary weight identity {}",
963                weight.id
964            )));
965        }
966        Ok(())
967    }
968}
969
970/// One rank's validated contiguous vocabulary ownership.
971#[derive(Debug, Clone, Eq, PartialEq)]
972pub struct VocabularyParallelRange {
973    /// Complete logical vocabulary size.
974    pub global_vocabulary: usize,
975    /// Half-open row range materialized by this rank.
976    pub local: std::ops::Range<usize>,
977}
978
979impl VocabularyParallelRange {
980    /// Validates non-empty in-bounds ownership.
981    pub fn validate(&self) -> Result<(), Error> {
982        if self.global_vocabulary == 0
983            || self.local.is_empty()
984            || self.local.end > self.global_vocabulary
985        {
986            return Err(Error::backend(format!(
987                "invalid vocabulary-parallel range {:?} of {}",
988                self.local, self.global_vocabulary
989            )));
990        }
991        Ok(())
992    }
993
994    /// Validates that an operator's declared global row count is exactly this
995    /// ownership range's global vocabulary.
996    pub fn validate_global_rows(&self, rows: i32) -> Result<(), Error> {
997        self.validate()?;
998        if usize::try_from(rows).ok() != Some(self.global_vocabulary) {
999            return Err(Error::backend(format!(
1000                "vocabulary-parallel operator declares {rows} rows but ownership covers {}",
1001                self.global_vocabulary
1002            )));
1003        }
1004        Ok(())
1005    }
1006}
1007
1008/// Token validation and sentinel behavior for one embedding lookup.
1009#[derive(Debug, Clone, Copy, Eq, PartialEq)]
1010pub enum EmbeddingLookupPolicy {
1011    /// Every token must be a non-negative row index in the embedding table.
1012    Strict,
1013    /// One negative token value produces an exact zero row instead of indexing
1014    /// the embedding table. Every other token remains subject to strict lookup.
1015    ZeroSentinel(i32),
1016}
1017
1018impl EmbeddingLookupPolicy {
1019    /// Validates that the optional sentinel cannot alias an ordinary row.
1020    pub fn validate(self) -> Result<(), Error> {
1021        if let Self::ZeroSentinel(sentinel) = self {
1022            if sentinel >= 0 {
1023                return Err(Error::backend(format!(
1024                    "embedding zero sentinel must be negative, got {sentinel}"
1025                )));
1026            }
1027        }
1028        Ok(())
1029    }
1030}
1031
1032/// One named, positive-width component of a fused projection output.
1033#[derive(Debug, Clone, Eq, PartialEq)]
1034pub struct FusedProjectionSegment {
1035    name: String,
1036    width: i32,
1037}
1038
1039impl FusedProjectionSegment {
1040    /// Creates a validated component declaration.
1041    pub fn new(name: impl Into<String>, width: i32) -> Result<Self, Error> {
1042        let name = name.into();
1043        if name.trim().is_empty() || width <= 0 {
1044            return Err(Error::backend(format!(
1045                "fused projection segments require a name and positive width, got name={name:?} width={width}"
1046            )));
1047        }
1048        Ok(Self { name, width })
1049    }
1050
1051    /// Returns the stable component name.
1052    pub fn name(&self) -> &str {
1053        &self.name
1054    }
1055
1056    /// Returns the component width on the final projection axis.
1057    pub const fn width(&self) -> i32 {
1058        self.width
1059    }
1060}
1061
1062/// Validated component-major layout of one fused affine projection.
1063#[derive(Debug, Clone, Eq, PartialEq)]
1064pub struct FusedProjectionLayout {
1065    segments: Vec<FusedProjectionSegment>,
1066    output_width: i32,
1067}
1068
1069impl FusedProjectionLayout {
1070    /// Validates ordered unique components and checked total width.
1071    pub fn new(segments: impl IntoIterator<Item = FusedProjectionSegment>) -> Result<Self, Error> {
1072        let segments = segments.into_iter().collect::<Vec<_>>();
1073        if segments.is_empty() {
1074            return Err(Error::backend(
1075                "fused projection layout must contain at least one segment",
1076            ));
1077        }
1078        let mut names = std::collections::BTreeSet::new();
1079        let mut output_width = 0i32;
1080        for segment in &segments {
1081            if !names.insert(segment.name.clone()) {
1082                return Err(Error::backend(format!(
1083                    "fused projection segment {:?} is duplicated",
1084                    segment.name
1085                )));
1086            }
1087            output_width = output_width.checked_add(segment.width).ok_or_else(|| {
1088                Error::backend("fused projection output width overflowed signed 32-bit geometry")
1089            })?;
1090        }
1091        Ok(Self {
1092            segments,
1093            output_width,
1094        })
1095    }
1096
1097    /// Returns component declarations in physical output order.
1098    pub fn segments(&self) -> &[FusedProjectionSegment] {
1099        &self.segments
1100    }
1101
1102    /// Returns the checked total output width.
1103    pub const fn output_width(&self) -> i32 {
1104        self.output_width
1105    }
1106
1107    /// Splits one fused result into component-major final-axis views.
1108    pub fn split<T: Tensor>(&self, output: &T, context: &T::Context) -> Result<Vec<T>, Error> {
1109        let actual = output
1110            .shape()
1111            .last()
1112            .copied()
1113            .ok_or_else(|| Error::backend("fused projection output has no feature axis"))?;
1114        if actual != self.output_width {
1115            return Err(Error::backend(format!(
1116                "fused projection emitted width {actual}, expected {}",
1117                self.output_width
1118            )));
1119        }
1120        let mut start = 0i32;
1121        let mut indexes = vec![Index::Full; output.shape().len()];
1122        self.segments
1123            .iter()
1124            .map(|segment| {
1125                let end = start + segment.width;
1126                let last = indexes.len() - 1;
1127                indexes[last] = Index::Range(start, end);
1128                let selected = output.index(&indexes, context);
1129                start = end;
1130                selected
1131            })
1132            .collect()
1133    }
1134}
1135
1136/// Parameterization policy for a reusable RMS normalization operator.
1137///
1138/// Architectures select the semantic scale form while the backend retains the
1139/// tensor implementation. In particular, learned-offset scales are evaluated
1140/// as `offset + weight`; the offset is not folded into checkpoint storage.
1141#[derive(Debug, Clone)]
1142pub enum NormalizationScale {
1143    /// Ordinary learned multiplicative scale.
1144    Learned(ParameterSpec),
1145    /// Learned scale offset by a fixed scalar at execution time.
1146    LearnedOffset {
1147        /// Stable checkpoint slot containing the learned offset tensor.
1148        weight: ParameterSpec,
1149        /// Fixed scalar added to every learned scale value.
1150        offset: f32,
1151    },
1152    /// RMS normalization without a learned scale.
1153    Unit,
1154}
1155
1156/// Complete construction policy for an RMS normalization operator.
1157#[derive(Debug, Clone)]
1158pub struct NormalizationConstructionSpec {
1159    /// Normalized feature count.
1160    pub dimensions: i32,
1161    /// Numerical stability epsilon.
1162    pub epsilon: f32,
1163    /// Learned, learned-offset, or weightless scale policy.
1164    pub scale: NormalizationScale,
1165}
1166
1167impl NormalizationConstructionSpec {
1168    /// Creates an RMS normalization with an ordinary learned scale.
1169    pub fn learned(dimensions: i32, epsilon: f32, weight: ParameterSpec) -> Self {
1170        Self {
1171            dimensions,
1172            epsilon,
1173            scale: NormalizationScale::Learned(weight),
1174        }
1175    }
1176
1177    /// Validates feature geometry and fixed scalar policy.
1178    pub fn validate(&self) -> Result<(), Error> {
1179        let offset = match &self.scale {
1180            NormalizationScale::LearnedOffset { offset, .. } => Some(*offset),
1181            NormalizationScale::Learned(_) | NormalizationScale::Unit => None,
1182        };
1183        if self.dimensions <= 0
1184            || !self.epsilon.is_finite()
1185            || self.epsilon <= 0.0
1186            || offset.is_some_and(|offset| !offset.is_finite())
1187        {
1188            return Err(Error::backend(format!(
1189                "invalid RMS normalization construction: dimensions={} epsilon={} offset={offset:?}",
1190                self.dimensions, self.epsilon
1191            )));
1192        }
1193        Ok(())
1194    }
1195}
1196
1197/// Fully normalized rotary-position algorithm selected by an architecture.
1198#[derive(Debug, Clone, Copy, PartialEq)]
1199pub enum RotaryAlgorithm {
1200    /// Unscaled rotary embeddings.
1201    Default,
1202    /// Uniform position interpolation by an extension factor.
1203    Linear {
1204        /// Context extension factor.
1205        factor: f32,
1206    },
1207    /// Llama 3 piecewise wavelength scaling.
1208    Llama3 {
1209        /// Context extension factor.
1210        factor: f32,
1211        /// Low-frequency wavelength boundary.
1212        low_frequency_factor: f32,
1213        /// High-frequency wavelength boundary.
1214        high_frequency_factor: f32,
1215        /// Context length used during training.
1216        original_max_positions: i32,
1217    },
1218    /// Rotary embeddings over a configurable prefix of each head.
1219    Proportional {
1220        /// Frequency scaling factor.
1221        factor: f32,
1222        /// Fraction of the head covered by rotary embeddings.
1223        rotary_fraction: f32,
1224    },
1225    /// YaRN frequency interpolation and attention concentration.
1226    Yarn {
1227        /// Context extension factor.
1228        factor: f32,
1229        /// Context length used during training.
1230        original_max_positions: i32,
1231        /// Fast correction rotation count.
1232        beta_fast: f32,
1233        /// Slow correction rotation count.
1234        beta_slow: f32,
1235        /// Rotary concentration coefficient.
1236        concentration: f32,
1237        /// All-dimension attention-scale coefficient.
1238        attention_factor: f32,
1239        /// Whether correction boundaries are rounded to integer frequency slots.
1240        truncate: bool,
1241    },
1242}
1243
1244impl RotaryAlgorithm {
1245    /// Validates the complete scalar geometry of this normalized algorithm.
1246    pub fn validate(self) -> Result<(), Error> {
1247        let positive = |value: f32| value.is_finite() && value > 0.0;
1248        let valid = match self {
1249            Self::Default => true,
1250            Self::Linear { factor } => positive(factor),
1251            Self::Llama3 {
1252                factor,
1253                low_frequency_factor,
1254                high_frequency_factor,
1255                original_max_positions,
1256            } => {
1257                positive(factor)
1258                    && positive(low_frequency_factor)
1259                    && positive(high_frequency_factor)
1260                    && high_frequency_factor > low_frequency_factor
1261                    && original_max_positions > 0
1262            }
1263            Self::Proportional {
1264                factor,
1265                rotary_fraction,
1266            } => positive(factor) && positive(rotary_fraction) && rotary_fraction <= 1.0,
1267            Self::Yarn {
1268                factor,
1269                original_max_positions,
1270                beta_fast,
1271                beta_slow,
1272                concentration,
1273                attention_factor,
1274                ..
1275            } => {
1276                positive(factor)
1277                    && original_max_positions > 0
1278                    && positive(beta_fast)
1279                    && positive(beta_slow)
1280                    && beta_fast > beta_slow
1281                    && positive(concentration)
1282                    && attention_factor.is_finite()
1283                    && attention_factor >= 0.0
1284            }
1285        };
1286        if valid {
1287            Ok(())
1288        } else {
1289            Err(Error::backend(format!(
1290                "invalid normalized rotary algorithm: {self:?}"
1291            )))
1292        }
1293    }
1294}
1295
1296/// Complete backend-neutral rotary-position construction specification.
1297#[derive(Debug, Clone, Copy)]
1298pub struct RotarySpec {
1299    /// Rotated head dimensions.
1300    pub dimensions: i32,
1301    /// Base frequency.
1302    pub base: f32,
1303    /// Whether adjacent pairs are rotated instead of split halves.
1304    pub traditional: bool,
1305    /// Fully normalized algorithm and scalar policy.
1306    pub algorithm: RotaryAlgorithm,
1307}
1308
1309/// Backend-native affine projection used by shared architectures.
1310pub trait LinearOperator<T: Tensor>: Clone + Debug + Parameterized<T> {
1311    /// Applies the projection without host materialization.
1312    fn forward(&mut self, input: &T, context: &T::Context) -> Result<T, Error>;
1313}
1314
1315/// Backend-native token embedding used by shared architectures.
1316pub trait EmbeddingOperator<T: Tensor>: Clone + Debug + Parameterized<T> {
1317    /// Looks up token embeddings.
1318    fn forward(&mut self, input: &T, context: &T::Context) -> Result<T, Error>;
1319    /// Looks up token embeddings under an explicit validation/sentinel policy.
1320    fn lookup(
1321        &mut self,
1322        input: &T,
1323        policy: EmbeddingLookupPolicy,
1324        context: &T::Context,
1325    ) -> Result<T, Error> {
1326        policy.validate()?;
1327        match policy {
1328            EmbeddingLookupPolicy::Strict => self.forward(input, context),
1329            EmbeddingLookupPolicy::ZeroSentinel(sentinel) => Err(Error::backend(format!(
1330                "embedding backend does not implement zero sentinel {sentinel}"
1331            ))),
1332        }
1333    }
1334    /// Projects hidden states through the transposed embedding table.
1335    fn as_linear(&mut self, input: &T, context: &T::Context) -> Result<T, Error>;
1336}
1337
1338/// Backend-native normalization operator.
1339pub trait NormalizationOperator<T: Tensor>: Clone + Debug + Parameterized<T> {
1340    /// Applies normalization.
1341    fn forward(&mut self, input: &T, context: &T::Context) -> Result<T, Error>;
1342}
1343
1344/// Construction specification for a normalized low-rank projection.
1345#[derive(Debug, Clone)]
1346pub struct LowRankProjectionSpec {
1347    /// Optional input-to-rank projection. When omitted, the input is already
1348    /// represented in rank space.
1349    pub first: Option<LinearSpec>,
1350    /// Normalization applied in rank space.
1351    pub normalization: NormalizationConstructionSpec,
1352    /// Rank-to-output projection.
1353    pub second: LinearSpec,
1354}
1355
1356impl LowRankProjectionSpec {
1357    /// Validates that both projections and normalization agree on rank width.
1358    pub fn validate(&self) -> Result<(), Error> {
1359        let rank = self.normalization.dimensions;
1360        if rank <= 0 {
1361            return Err(Error::backend(format!(
1362                "low-rank normalization dimensions must be positive, got {rank}"
1363            )));
1364        }
1365        if self.second.input != rank {
1366            return Err(Error::backend(format!(
1367                "low-rank second projection expects {} inputs but rank width is {rank}",
1368                self.second.input
1369            )));
1370        }
1371        if let Some(first) = &self.first {
1372            if first.output != rank {
1373                return Err(Error::backend(format!(
1374                    "low-rank first projection emits {} values but rank width is {rank}",
1375                    first.output
1376                )));
1377            }
1378        }
1379        Ok(())
1380    }
1381}
1382
1383/// Reusable normalized low-rank projection with statically dispatched backend
1384/// operators.
1385#[derive(Debug, Clone, Parameterized)]
1386#[parameterized(tensor = "B::Tensor")]
1387pub struct LowRankProjection<B: NeuralBackend> {
1388    /// Optional input-to-rank projection.
1389    pub first: Option<B::Linear>,
1390    /// Rank-space normalization.
1391    pub normalization: B::Normalization,
1392    /// Rank-to-output projection.
1393    pub second: B::Linear,
1394}
1395
1396impl<B: NeuralBackend> LowRankProjection<B> {
1397    /// Builds an unloaded low-rank projection from architecture-owned
1398    /// parameter identities and physical formats.
1399    pub fn new(
1400        spec: LowRankProjectionSpec,
1401        context: &<B::Tensor as Tensor>::Context,
1402    ) -> Result<Self, Error> {
1403        spec.validate()?;
1404        Ok(Self {
1405            first: spec
1406                .first
1407                .map(|projection| B::linear(projection, context))
1408                .transpose()?,
1409            normalization: B::normalization(spec.normalization, context)?,
1410            second: B::linear(spec.second, context)?,
1411        })
1412    }
1413
1414    /// Applies the optional first projection, rank normalization, and second
1415    /// projection without backend-value conversion.
1416    pub fn forward(
1417        &mut self,
1418        input: &B::Tensor,
1419        context: &<B::Tensor as Tensor>::Context,
1420    ) -> Result<B::Tensor, Error> {
1421        let rank = match &mut self.first {
1422            Some(first) => first.forward(input, context)?,
1423            None => input.clone(),
1424        };
1425        let rank = self.normalization.forward(&rank, context)?;
1426        self.second.forward(&rank, context)
1427    }
1428}
1429
1430/// Backend-native rotary-position operator.
1431pub trait RotaryOperator<T: Tensor>: Clone + Debug + Parameterized<T> {
1432    /// Applies the architecture-selected rotary position source.
1433    fn forward(
1434        &mut self,
1435        input: &T,
1436        position: RotaryPosition<'_, T>,
1437        context: &T::Context,
1438    ) -> Result<T, Error>;
1439
1440    /// Applies rotary positions only to the selected final-axis subspace and
1441    /// leaves every other feature unchanged.
1442    fn forward_subspace(
1443        &mut self,
1444        input: &T,
1445        subspace: RotarySubspace,
1446        position: RotaryPosition<'_, T>,
1447        context: &T::Context,
1448    ) -> Result<T, Error> {
1449        let width = *input
1450            .shape()
1451            .last()
1452            .ok_or_else(|| Error::backend("rotary input must have a feature axis"))?;
1453        let (start, dimensions) = subspace.resolve(width)?;
1454        if start == 0 && dimensions == width {
1455            return self.forward(input, position, context);
1456        }
1457        let end = start + dimensions;
1458        let mut indexes = vec![Index::Full; input.shape().len()];
1459        indexes[input.shape().len() - 1] = Index::Range(start, end);
1460        let selected = input.index(&indexes, context)?;
1461        let rotated = self.forward(&selected, position, context)?;
1462        let mut pieces = Vec::with_capacity(3);
1463        if start > 0 {
1464            indexes[input.shape().len() - 1] = Index::Range(0, start);
1465            pieces.push(input.index(&indexes, context)?);
1466        }
1467        pieces.push(rotated);
1468        if end < width {
1469            indexes[input.shape().len() - 1] = Index::Range(end, width);
1470            pieces.push(input.index(&indexes, context)?);
1471        }
1472        T::concatenate(&pieces, -1, context)
1473    }
1474}
1475
1476/// Final-axis feature range selected for rotary position encoding.
1477#[derive(Debug, Clone, Copy, Eq, PartialEq)]
1478pub enum RotarySubspace {
1479    /// Rotate the complete final feature axis.
1480    Full,
1481    /// Rotate one contiguous half-open final-axis range.
1482    Range {
1483        /// First rotated feature.
1484        start: i32,
1485        /// Number of rotated features.
1486        dimensions: i32,
1487    },
1488}
1489
1490impl RotarySubspace {
1491    fn resolve(self, width: i32) -> Result<(i32, i32), Error> {
1492        let (start, dimensions) = match self {
1493            Self::Full => (0, width),
1494            Self::Range { start, dimensions } => (start, dimensions),
1495        };
1496        if width <= 0
1497            || start < 0
1498            || dimensions <= 0
1499            || dimensions % 2 != 0
1500            || start > width - dimensions
1501        {
1502            return Err(Error::backend(format!(
1503                "rotary subspace start={start} dimensions={dimensions} is invalid for width {width}"
1504            )));
1505        }
1506        Ok((start, dimensions))
1507    }
1508}
1509
1510/// Position data supplied to a backend-native rotary operator.
1511#[derive(Debug)]
1512pub enum RotaryPosition<'a, T> {
1513    /// Ordinary contiguous sequence positions beginning at this offset.
1514    Offset(i32),
1515    /// Caller-provided cosine and sine tensors for explicit positions.
1516    Embeddings {
1517        /// Cosine values shaped for the input sequence and rotary dimensions.
1518        cosine: &'a T,
1519        /// Sine values shaped for the input sequence and rotary dimensions.
1520        sine: &'a T,
1521    },
1522}
1523
1524impl<T> Copy for RotaryPosition<'_, T> {}
1525
1526impl<T> Clone for RotaryPosition<'_, T> {
1527    fn clone(&self) -> Self {
1528        *self
1529    }
1530}
1531
1532/// Architecture-selected scoring policy for top-k group selection.
1533#[derive(Debug, Clone, Copy, Eq, PartialEq)]
1534#[non_exhaustive]
1535pub enum GroupScoring {
1536    /// Apply softmax across all group logits before selection.
1537    Softmax,
1538    /// Select the largest raw logits, then softmax only the selected entries.
1539    SelectedSoftmax,
1540    /// Apply elementwise sigmoid scores before grouped selection.
1541    Sigmoid,
1542    /// Apply the square root of softplus before selection.
1543    SqrtSoftplus,
1544}
1545
1546/// Backend-neutral top-k group-selection semantics.
1547#[derive(Debug, Clone, Copy, PartialEq)]
1548pub struct TopKGroupSelectionSpec {
1549    group_count: i32,
1550    top_k: i32,
1551    scoring: GroupScoring,
1552    normalize_selected: bool,
1553    normalization_epsilon: f32,
1554    coefficient_scale: f32,
1555    selection_partitions: i32,
1556    selected_groups: i32,
1557}
1558
1559/// Construction specification for a learned top-k selector projection.
1560#[derive(Debug, Clone)]
1561pub struct TopKGroupSelectorSpec {
1562    /// Hidden width consumed by the selector projection.
1563    input_dimensions: i32,
1564    /// Stable selector projection parameter identity.
1565    weight: ParameterSpec,
1566    /// Optional ordinary projection bias. This contributes to selector logits
1567    /// before scoring, selection, and selected-score normalization.
1568    bias: Option<ParameterSpec>,
1569    /// Optional correction bias used only to choose group IDs. Gathered
1570    /// selection scores remain unbiased.
1571    correction_bias: Option<ParameterSpec>,
1572    /// Optional learned scaling applied after weightless RMS normalization of
1573    /// the selector input.
1574    input_transform: Option<SelectorInputTransformSpec>,
1575    /// Optional learned per-group multiplier gathered after selection.
1576    coefficient_scale: Option<ParameterSpec>,
1577    /// Physical encoding and exact companion identities of the selector projection.
1578    format: LinearFormatSpec,
1579    /// Architecture-selected scoring and selection semantics.
1580    selection: TopKGroupSelectionSpec,
1581}
1582
1583/// Learned normalization and scale applied before a selector projection.
1584#[derive(Debug, Clone)]
1585pub struct SelectorInputTransformSpec {
1586    /// Weightless RMS-normalization epsilon.
1587    epsilon: f32,
1588    /// Learned feature-wise multiplier.
1589    scale: ParameterSpec,
1590    /// Whether to additionally multiply by `1 / sqrt(input_dimensions)`.
1591    inverse_sqrt_dimensions: bool,
1592}
1593
1594impl SelectorInputTransformSpec {
1595    /// Creates a validated selector-input transformation.
1596    pub fn new(
1597        epsilon: f32,
1598        scale: ParameterSpec,
1599        inverse_sqrt_dimensions: bool,
1600    ) -> Result<Self, Error> {
1601        if !epsilon.is_finite() || epsilon < 0.0 {
1602            return Err(Error::backend(
1603                "selector input RMS epsilon must be finite and nonnegative",
1604            ));
1605        }
1606        Ok(Self {
1607            epsilon,
1608            scale,
1609            inverse_sqrt_dimensions,
1610        })
1611    }
1612
1613    /// Returns the RMS epsilon.
1614    pub const fn epsilon(&self) -> f32 {
1615        self.epsilon
1616    }
1617    /// Returns the learned input scale.
1618    pub const fn scale(&self) -> &ParameterSpec {
1619        &self.scale
1620    }
1621    /// Returns whether inverse-square-root width scaling is selected.
1622    pub const fn inverse_sqrt_dimensions(&self) -> bool {
1623        self.inverse_sqrt_dimensions
1624    }
1625}
1626
1627impl TopKGroupSelectorSpec {
1628    /// Creates a selector projection with no optional parameters.
1629    pub fn new(
1630        input_dimensions: i32,
1631        weight: ParameterSpec,
1632        format: LinearFormatSpec,
1633        selection: TopKGroupSelectionSpec,
1634    ) -> Result<Self, Error> {
1635        let spec = Self {
1636            input_dimensions,
1637            weight,
1638            bias: None,
1639            correction_bias: None,
1640            input_transform: None,
1641            coefficient_scale: None,
1642            format,
1643            selection,
1644        };
1645        spec.validate()?;
1646        Ok(spec)
1647    }
1648
1649    /// Adds an ordinary projection bias.
1650    pub fn with_bias(mut self, bias: ParameterSpec) -> Result<Self, Error> {
1651        self.bias = Some(bias);
1652        self.validate()?;
1653        Ok(self)
1654    }
1655    /// Adds a selection-only correction bias.
1656    pub fn with_correction_bias(mut self, bias: ParameterSpec) -> Result<Self, Error> {
1657        self.correction_bias = Some(bias);
1658        self.validate()?;
1659        Ok(self)
1660    }
1661    /// Adds an input normalization transformation.
1662    pub fn with_input_transform(mut self, transform: SelectorInputTransformSpec) -> Self {
1663        self.input_transform = Some(transform);
1664        self
1665    }
1666    /// Adds a learned coefficient multiplier.
1667    pub fn with_coefficient_scale(mut self, scale: ParameterSpec) -> Self {
1668        self.coefficient_scale = Some(scale);
1669        self
1670    }
1671    /// Returns the input width.
1672    pub const fn input_dimensions(&self) -> i32 {
1673        self.input_dimensions
1674    }
1675    /// Returns the selector projection parameter.
1676    pub const fn weight(&self) -> &ParameterSpec {
1677        &self.weight
1678    }
1679    /// Returns the ordinary projection bias.
1680    pub const fn bias(&self) -> Option<&ParameterSpec> {
1681        self.bias.as_ref()
1682    }
1683    /// Returns the selection correction bias.
1684    pub const fn correction_bias(&self) -> Option<&ParameterSpec> {
1685        self.correction_bias.as_ref()
1686    }
1687    /// Returns the optional input transformation.
1688    pub const fn input_transform(&self) -> Option<&SelectorInputTransformSpec> {
1689        self.input_transform.as_ref()
1690    }
1691    /// Returns the learned coefficient multiplier.
1692    pub const fn coefficient_scale(&self) -> Option<&ParameterSpec> {
1693        self.coefficient_scale.as_ref()
1694    }
1695    /// Returns the physical projection format.
1696    pub const fn format(&self) -> &LinearFormatSpec {
1697        &self.format
1698    }
1699    /// Returns the group-selection semantics.
1700    pub const fn selection(&self) -> TopKGroupSelectionSpec {
1701        self.selection
1702    }
1703
1704    /// Validates positive input geometry.
1705    pub fn validate(&self) -> Result<(), Error> {
1706        self.format.validate_for_weight(&self.weight)?;
1707        if self.input_dimensions <= 0 {
1708            return Err(Error::backend(format!(
1709                "selector input dimensions must be positive, got {}",
1710                self.input_dimensions
1711            )));
1712        }
1713        if self
1714            .input_transform
1715            .as_ref()
1716            .is_some_and(|transform| !transform.epsilon.is_finite() || transform.epsilon < 0.0)
1717        {
1718            return Err(Error::backend(
1719                "selector input RMS epsilon must be finite and nonnegative",
1720            ));
1721        }
1722        if self
1723            .bias
1724            .as_ref()
1725            .zip(self.correction_bias.as_ref())
1726            .is_some_and(|(bias, correction_bias)| bias.id == correction_bias.id)
1727        {
1728            return Err(Error::backend(
1729                "selector projection bias and correction bias require distinct parameter identities",
1730            ));
1731        }
1732        Ok(())
1733    }
1734}
1735
1736impl TopKGroupSelectionSpec {
1737    /// Creates a validated top-k group-selection policy.
1738    pub fn new(
1739        group_count: i32,
1740        top_k: i32,
1741        scoring: GroupScoring,
1742        normalize_selected: bool,
1743    ) -> Result<Self, Error> {
1744        if group_count <= 0 {
1745            return Err(Error::backend(format!(
1746                "group count must be positive, got {group_count}"
1747            )));
1748        }
1749        if top_k <= 0 || top_k > group_count {
1750            return Err(Error::backend(format!(
1751                "top-k selection count must be in 1..={group_count}, got {top_k}"
1752            )));
1753        }
1754        Ok(Self {
1755            group_count,
1756            top_k,
1757            scoring,
1758            normalize_selected,
1759            normalization_epsilon: 0.0,
1760            coefficient_scale: 1.0,
1761            selection_partitions: 1,
1762            selected_groups: 1,
1763        })
1764    }
1765
1766    /// Selects grouped selection semantics.
1767    pub fn with_groups(
1768        mut self,
1769        selection_partitions: i32,
1770        selected_groups: i32,
1771    ) -> Result<Self, Error> {
1772        if selection_partitions <= 0
1773            || selected_groups <= 0
1774            || selected_groups > selection_partitions
1775            || self.group_count % selection_partitions != 0
1776            || self.top_k > selected_groups * (self.group_count / selection_partitions)
1777        {
1778            return Err(Error::backend(format!(
1779                "invalid grouped selection geometry: group_count={} top_k={} partitions={selection_partitions} selected_partitions={selected_groups}",
1780                self.group_count, self.top_k
1781            )));
1782        }
1783        self.selection_partitions = selection_partitions;
1784        self.selected_groups = selected_groups;
1785        Ok(self)
1786    }
1787
1788    /// Selects denominator epsilon and final grouped contribution scale.
1789    pub fn with_weight_policy(
1790        mut self,
1791        normalization_epsilon: f32,
1792        coefficient_scale: f32,
1793    ) -> Result<Self, Error> {
1794        if !normalization_epsilon.is_finite()
1795            || normalization_epsilon < 0.0
1796            || !coefficient_scale.is_finite()
1797            || coefficient_scale <= 0.0
1798        {
1799            return Err(Error::backend(
1800                "selection normalization epsilon must be finite and nonnegative and grouped scaling must be finite and positive",
1801            ));
1802        }
1803        self.normalization_epsilon = normalization_epsilon;
1804        self.coefficient_scale = coefficient_scale;
1805        Ok(self)
1806    }
1807
1808    /// Returns the total number of selectable groups.
1809    pub const fn group_count(self) -> i32 {
1810        self.group_count
1811    }
1812
1813    /// Returns the number of selected groups per token.
1814    pub const fn top_k(self) -> i32 {
1815        self.top_k
1816    }
1817
1818    /// Returns the score transformation applied before selection.
1819    pub const fn scoring(self) -> GroupScoring {
1820        self.scoring
1821    }
1822
1823    /// Returns whether selected scores are renormalized to sum to one.
1824    pub const fn normalize_selected(self) -> bool {
1825        self.normalize_selected
1826    }
1827
1828    /// Returns the epsilon added to selected-score normalization.
1829    pub const fn normalization_epsilon(self) -> f32 {
1830        self.normalization_epsilon
1831    }
1832
1833    /// Returns the final grouped contribution multiplier.
1834    pub const fn coefficient_scale(self) -> f32 {
1835        self.coefficient_scale
1836    }
1837
1838    /// Returns the number of equal contiguous groups.
1839    pub const fn selection_partitions(self) -> i32 {
1840        self.selection_partitions
1841    }
1842
1843    /// Returns the number of groups eligible for group selection.
1844    pub const fn selected_groups(self) -> i32 {
1845        self.selected_groups
1846    }
1847}
1848
1849/// Backend-native result of one top-k group selection.
1850#[derive(Debug, Clone)]
1851pub struct GroupSelection<T> {
1852    /// Selected group IDs shaped `[..., top_k]`.
1853    group_indices: T,
1854    /// Selected scores before optional top-k renormalization.
1855    selected_scores: T,
1856    /// Final normalized or unnormalized selection weights.
1857    coefficients: T,
1858}
1859
1860impl<T> GroupSelection<T> {
1861    /// Creates one selected group batch.
1862    pub fn new(group_indices: T, selected_scores: T, coefficients: T) -> Self {
1863        Self {
1864            group_indices,
1865            selected_scores,
1866            coefficients,
1867        }
1868    }
1869    /// Returns selected group indices.
1870    pub const fn group_indices(&self) -> &T {
1871        &self.group_indices
1872    }
1873    /// Returns selected pre-normalization scores.
1874    pub const fn selected_scores(&self) -> &T {
1875        &self.selected_scores
1876    }
1877    /// Returns final group coefficients.
1878    pub const fn coefficients(&self) -> &T {
1879        &self.coefficients
1880    }
1881}
1882
1883/// Geometry for joint selected-group and always-on-group weighting.
1884#[derive(Debug, Clone, Copy, PartialEq)]
1885pub struct JointGroupSelectionSpec {
1886    selectable_groups: i32,
1887    always_on_groups: i32,
1888    top_k: i32,
1889    coefficient_scale: f32,
1890}
1891
1892impl JointGroupSelectionSpec {
1893    /// Creates validated joint-selection geometry.
1894    pub fn new(
1895        selectable_groups: i32,
1896        always_on_groups: i32,
1897        top_k: i32,
1898        coefficient_scale: f32,
1899    ) -> Result<Self, Error> {
1900        if selectable_groups <= 0
1901            || always_on_groups <= 0
1902            || top_k <= 0
1903            || top_k > selectable_groups
1904            || !coefficient_scale.is_finite()
1905            || coefficient_scale <= 0.0
1906        {
1907            return Err(Error::backend(format!(
1908                "invalid joint group-selection geometry selectable={selectable_groups} always_on={always_on_groups} top_k={top_k} coefficient_scale={coefficient_scale}"
1909            )));
1910        }
1911        Ok(Self {
1912            selectable_groups,
1913            always_on_groups,
1914            top_k,
1915            coefficient_scale,
1916        })
1917    }
1918
1919    /// Returns the number of selectable groups.
1920    pub const fn selectable_groups(self) -> i32 {
1921        self.selectable_groups
1922    }
1923
1924    /// Returns the number of always-on groups.
1925    pub const fn always_on_groups(self) -> i32 {
1926        self.always_on_groups
1927    }
1928
1929    /// Returns the selected group count per row.
1930    pub const fn top_k(self) -> i32 {
1931        self.top_k
1932    }
1933
1934    /// Returns the fixed coefficient multiplier.
1935    pub const fn coefficient_scale(self) -> f32 {
1936        self.coefficient_scale
1937    }
1938}
1939
1940/// Joint sigmoid selection request with selected groups and always-on
1941/// shared groups normalized in one probability distribution.
1942#[derive(Debug, Clone, Copy)]
1943pub struct JointGroupSelectionInput<'a, T> {
1944    /// Hidden states shaped `[..., hidden]`.
1945    hidden: &'a T,
1946    /// Projection shaped `[selectable_groups + always_on_groups, hidden]`.
1947    weight: &'a T,
1948    /// Correction bias used only for grouped top-k selection.
1949    correction_bias: &'a T,
1950    /// Learned scalar multiplier applied to all final selection weights.
1951    global_scale: &'a T,
1952    /// Joint-selection geometry.
1953    selection: JointGroupSelectionSpec,
1954}
1955
1956impl<'a, T: Tensor> JointGroupSelectionInput<'a, T> {
1957    /// Creates a validated joint group-selection request.
1958    pub fn new(
1959        hidden: &'a T,
1960        weight: &'a T,
1961        correction_bias: &'a T,
1962        global_scale: &'a T,
1963        selection: JointGroupSelectionSpec,
1964    ) -> Result<Self, Error> {
1965        let input = Self {
1966            hidden,
1967            weight,
1968            correction_bias,
1969            global_scale,
1970            selection,
1971        };
1972        input.validate()?;
1973        Ok(input)
1974    }
1975    /// Returns hidden states.
1976    pub const fn hidden(&self) -> &'a T {
1977        self.hidden
1978    }
1979    /// Returns the selection projection.
1980    pub const fn weight(&self) -> &'a T {
1981        self.weight
1982    }
1983    /// Returns the selection-only correction bias.
1984    pub const fn correction_bias(&self) -> &'a T {
1985        self.correction_bias
1986    }
1987    /// Returns the learned global scale.
1988    pub const fn global_scale(&self) -> &'a T {
1989        self.global_scale
1990    }
1991    /// Returns the number of selectable groups.
1992    pub const fn selectable_groups(&self) -> i32 {
1993        self.selection.selectable_groups()
1994    }
1995    /// Returns the number of always-on groups.
1996    pub const fn always_on_groups(&self) -> i32 {
1997        self.selection.always_on_groups()
1998    }
1999    /// Returns the selected group count per row.
2000    pub const fn top_k(&self) -> i32 {
2001        self.selection.top_k()
2002    }
2003    /// Returns the fixed coefficient multiplier.
2004    pub const fn coefficient_scale(&self) -> f32 {
2005        self.selection.coefficient_scale()
2006    }
2007}
2008
2009impl<T: Tensor> JointGroupSelectionInput<'_, T> {
2010    /// Validates exact projection, bias, and scalar geometry.
2011    pub fn validate(&self) -> Result<(), Error> {
2012        let hidden = self.hidden.shape();
2013        let weight = self.weight.shape();
2014        let bias = self.correction_bias.shape();
2015        let scale = self.global_scale.shape();
2016        let hidden_width = hidden.last().copied().unwrap_or(0);
2017        if hidden.len() < 2
2018            || weight
2019                != [
2020                    self.selectable_groups() + self.always_on_groups(),
2021                    hidden_width,
2022                ]
2023            || bias != [self.selectable_groups()]
2024            || scale != [1]
2025        {
2026            return Err(Error::backend(format!(
2027                "invalid joint group selection tensors hidden={hidden:?} weight={weight:?} bias={bias:?} scale={scale:?} selectable={} always_on={} top_k={}",
2028                self.selectable_groups(),
2029                self.always_on_groups(),
2030                self.top_k(),
2031            )));
2032        }
2033        Ok(())
2034    }
2035}
2036
2037/// Backend-native result of joint grouped and shared group weighting.
2038#[derive(Debug, Clone)]
2039pub struct JointGroupSelection<T> {
2040    /// Selected primary group IDs shaped `[tokens, top_k]`; integer dtype is
2041    /// backend-defined and accepted by the corresponding group operator.
2042    primary_indices: T,
2043    /// Grouped group weights shaped `[tokens, top_k]`.
2044    primary_coefficients: T,
2045    /// Always-on shared group weights shaped `[tokens, always_on_groups]`.
2046    always_on_coefficients: T,
2047}
2048
2049impl<T> JointGroupSelection<T> {
2050    /// Creates one joint selection result.
2051    pub fn new(primary_indices: T, primary_coefficients: T, always_on_coefficients: T) -> Self {
2052        Self {
2053            primary_indices,
2054            primary_coefficients,
2055            always_on_coefficients,
2056        }
2057    }
2058    /// Returns selected primary-group indices.
2059    pub const fn primary_indices(&self) -> &T {
2060        &self.primary_indices
2061    }
2062    /// Returns primary-group coefficients.
2063    pub const fn primary_coefficients(&self) -> &T {
2064        &self.primary_coefficients
2065    }
2066    /// Returns always-on group coefficients.
2067    pub const fn always_on_coefficients(&self) -> &T {
2068        &self.always_on_coefficients
2069    }
2070}
2071
2072/// Activation applied to the gate branch of a grouped gated product.
2073#[derive(Debug, Clone, Copy, Eq, PartialEq)]
2074#[non_exhaustive]
2075pub enum GatedProductActivation {
2076    /// `gate * sigmoid(sigmoid_multiplier * gate)`.
2077    Silu,
2078    /// Approximate Gaussian error linear unit.
2079    GeluApproximate,
2080}
2081
2082/// Validated equation policy for a gated product group.
2083#[derive(Debug, Clone, Copy, PartialEq)]
2084pub struct GatedProductPolicy {
2085    activation: GatedProductActivation,
2086    gate_upper_bound: Option<f32>,
2087    up_absolute_bound: Option<f32>,
2088    sigmoid_multiplier: f32,
2089    up_offset: f32,
2090}
2091
2092impl GatedProductPolicy {
2093    /// Creates a validated gated-product equation.
2094    pub fn new(
2095        activation: GatedProductActivation,
2096        gate_upper_bound: Option<f32>,
2097        up_absolute_bound: Option<f32>,
2098        sigmoid_multiplier: f32,
2099        up_offset: f32,
2100    ) -> Result<Self, Error> {
2101        let policy = Self {
2102            activation,
2103            gate_upper_bound,
2104            up_absolute_bound,
2105            sigmoid_multiplier,
2106            up_offset,
2107        };
2108        policy.validate()?;
2109        Ok(policy)
2110    }
2111
2112    /// Ordinary unbounded SiLU gating.
2113    pub const fn ordinary_silu() -> Self {
2114        Self {
2115            activation: GatedProductActivation::Silu,
2116            gate_upper_bound: None,
2117            up_absolute_bound: None,
2118            sigmoid_multiplier: 1.0,
2119            up_offset: 0.0,
2120        }
2121    }
2122
2123    /// Ordinary unbounded approximate-GELU gating.
2124    pub const fn ordinary_gelu_approximate() -> Self {
2125        Self {
2126            activation: GatedProductActivation::GeluApproximate,
2127            ..Self::ordinary_silu()
2128        }
2129    }
2130
2131    /// Creates ordinary SiLU gating with the same positive gate and up bound.
2132    pub fn bounded_silu(bound: f32) -> Result<Self, Error> {
2133        Self::new(
2134            GatedProductActivation::Silu,
2135            Some(bound),
2136            Some(bound),
2137            1.0,
2138            0.0,
2139        )
2140    }
2141
2142    /// Validates finite scalars and positive bounds/multiplier.
2143    pub fn validate(self) -> Result<(), Error> {
2144        if self
2145            .gate_upper_bound
2146            .is_some_and(|bound| !bound.is_finite() || bound <= 0.0)
2147            || self
2148                .up_absolute_bound
2149                .is_some_and(|bound| !bound.is_finite() || bound <= 0.0)
2150            || !self.sigmoid_multiplier.is_finite()
2151            || self.sigmoid_multiplier <= 0.0
2152            || !self.up_offset.is_finite()
2153        {
2154            return Err(Error::backend(format!(
2155                "invalid gated-product policy: {self:?}"
2156            )));
2157        }
2158        Ok(())
2159    }
2160
2161    /// Gate activation.
2162    pub const fn activation(self) -> GatedProductActivation {
2163        self.activation
2164    }
2165
2166    /// Optional upper bound applied to the gate branch before activation.
2167    pub const fn gate_upper_bound(self) -> Option<f32> {
2168        self.gate_upper_bound
2169    }
2170
2171    /// Optional symmetric absolute bound applied to the up branch.
2172    pub const fn up_absolute_bound(self) -> Option<f32> {
2173        self.up_absolute_bound
2174    }
2175
2176    /// Multiplier inside the sigmoid for SiLU gating.
2177    pub const fn sigmoid_multiplier(self) -> f32 {
2178        self.sigmoid_multiplier
2179    }
2180
2181    /// Offset added to the up branch after optional clipping.
2182    pub const fn up_offset(self) -> f32 {
2183        self.up_offset
2184    }
2185}
2186
2187impl Default for GatedProductPolicy {
2188    fn default() -> Self {
2189        Self::ordinary_silu()
2190    }
2191}
2192
2193/// Statically dispatched top-k selector.
2194pub trait GroupSelectionOperator<T: Tensor>: Clone + Debug + Parameterized<T> {
2195    /// Selects group IDs and selection weights without host materialization.
2196    fn select(&mut self, logits: &T, context: &T::Context) -> Result<GroupSelection<T>, Error>;
2197
2198    /// Computes scores and weights for caller-selected global group IDs.
2199    fn select_indices(
2200        &mut self,
2201        input: &T,
2202        group_indices: &T,
2203        context: &T::Context,
2204    ) -> Result<GroupSelection<T>, Error>;
2205}
2206
2207/// Parameter identities for one gated-product group or one packed group axis.
2208#[derive(Debug, Clone)]
2209pub struct GatedProductGroupParameters {
2210    /// Gating projection weight.
2211    gate: GroupedProjectionSpec,
2212    /// Up projection weight.
2213    up: GroupedProjectionSpec,
2214    /// Down projection weight.
2215    down: GroupedProjectionSpec,
2216}
2217
2218impl GatedProductGroupParameters {
2219    /// Creates one independently addressable gated-product parameter group.
2220    pub fn new(
2221        gate: GroupedProjectionSpec,
2222        up: GroupedProjectionSpec,
2223        down: GroupedProjectionSpec,
2224    ) -> Self {
2225        Self { gate, up, down }
2226    }
2227    /// Returns the gate projection.
2228    pub const fn gate(&self) -> &GroupedProjectionSpec {
2229        &self.gate
2230    }
2231    /// Returns the up projection.
2232    pub const fn up(&self) -> &GroupedProjectionSpec {
2233        &self.up
2234    }
2235    /// Returns the down projection.
2236    pub const fn down(&self) -> &GroupedProjectionSpec {
2237        &self.down
2238    }
2239}
2240
2241/// One group projection identity and optional physical encoding.
2242#[derive(Debug, Clone)]
2243pub struct GroupedProjectionSpec {
2244    /// Stable logical parameter identity.
2245    weight: ParameterSpec,
2246    /// Optional ordinary per-output projection bias.
2247    bias: Option<ParameterSpec>,
2248    /// Complete physical checkpoint encoding and exact companion identities.
2249    format: LinearFormatSpec,
2250}
2251
2252impl GroupedProjectionSpec {
2253    /// Creates one validated grouped projection description.
2254    pub fn new(
2255        weight: ParameterSpec,
2256        bias: Option<ParameterSpec>,
2257        format: LinearFormatSpec,
2258    ) -> Result<Self, Error> {
2259        let spec = Self {
2260            weight,
2261            bias,
2262            format,
2263        };
2264        spec.validate()?;
2265        Ok(spec)
2266    }
2267    /// Returns the matrix parameter.
2268    pub const fn weight(&self) -> &ParameterSpec {
2269        &self.weight
2270    }
2271    /// Returns the optional output bias.
2272    pub const fn bias(&self) -> Option<&ParameterSpec> {
2273        self.bias.as_ref()
2274    }
2275    /// Returns the physical matrix format.
2276    pub const fn format(&self) -> &LinearFormatSpec {
2277        &self.format
2278    }
2279    fn validate(&self) -> Result<(), Error> {
2280        self.format.validate_for_weight(&self.weight)?;
2281        let parameters = self.parameters();
2282        for (index, parameter) in parameters.iter().enumerate() {
2283            if parameters[index + 1..]
2284                .iter()
2285                .any(|candidate| candidate.id == parameter.id)
2286            {
2287                return Err(Error::backend(format!(
2288                    "grouped projection reuses parameter identity {:?}",
2289                    parameter.id
2290                )));
2291            }
2292        }
2293        Ok(())
2294    }
2295
2296    fn parameters(&self) -> Vec<&ParameterSpec> {
2297        let mut parameters = vec![&self.weight];
2298        parameters.extend(self.bias.as_ref());
2299        parameters.extend(self.format.scale());
2300        parameters.extend(self.format.affine_bias());
2301        parameters
2302    }
2303}
2304
2305/// Logical parameter layout for a gated-product bank.
2306#[derive(Debug, Clone)]
2307#[allow(clippy::large_enum_variant)] // Packed layout stays inline on the construction hot path.
2308#[non_exhaustive]
2309pub enum GatedProductGroupLayout {
2310    /// Component-major fused gate-then-up and down tensors whose leading axis
2311    /// indexes groups.
2312    Packed {
2313        /// Concatenated gate/up projection.
2314        gate_up: GroupedProjectionSpec,
2315        /// Down projection.
2316        down: GroupedProjectionSpec,
2317    },
2318    /// Independently materialized group parameter triples in group-ID order.
2319    Independent(Vec<GatedProductGroupParameters>),
2320}
2321
2322/// Complete architecture-owned construction specification for grouped gated-product groups.
2323#[derive(Debug, Clone)]
2324pub struct GroupedGatedProductSpec {
2325    /// Number of groups.
2326    group_count: i32,
2327    /// Input hidden width.
2328    input_dimensions: i32,
2329    /// Per-group intermediate width.
2330    intermediate_dimensions: i32,
2331    /// Output hidden width.
2332    output_dimensions: i32,
2333    /// Exact gate activation, bounds, multiplier, and up offset.
2334    policy: GatedProductPolicy,
2335    /// Stable logical parameter identities and storage organization.
2336    layout: GatedProductGroupLayout,
2337}
2338
2339impl GroupedGatedProductSpec {
2340    /// Creates one validated grouped gated-product mechanism request.
2341    pub fn new(
2342        group_count: i32,
2343        input_dimensions: i32,
2344        intermediate_dimensions: i32,
2345        output_dimensions: i32,
2346        policy: GatedProductPolicy,
2347        layout: GatedProductGroupLayout,
2348    ) -> Result<Self, Error> {
2349        let spec = Self {
2350            group_count,
2351            input_dimensions,
2352            intermediate_dimensions,
2353            output_dimensions,
2354            policy,
2355            layout,
2356        };
2357        spec.validate()?;
2358        Ok(spec)
2359    }
2360    /// Returns a copy with placement-resolved group geometry.
2361    pub fn with_group_geometry(
2362        mut self,
2363        group_count: i32,
2364        intermediate_dimensions: i32,
2365    ) -> Result<Self, Error> {
2366        self.group_count = group_count;
2367        self.intermediate_dimensions = intermediate_dimensions;
2368        self.validate()?;
2369        Ok(self)
2370    }
2371    /// Returns the number of parameter groups.
2372    pub const fn group_count(&self) -> i32 {
2373        self.group_count
2374    }
2375    /// Returns the input width.
2376    pub const fn input_dimensions(&self) -> i32 {
2377        self.input_dimensions
2378    }
2379    /// Returns the per-group intermediate width.
2380    pub const fn intermediate_dimensions(&self) -> i32 {
2381        self.intermediate_dimensions
2382    }
2383    /// Returns the output width.
2384    pub const fn output_dimensions(&self) -> i32 {
2385        self.output_dimensions
2386    }
2387    /// Returns the gated-product equation policy.
2388    pub const fn policy(&self) -> GatedProductPolicy {
2389        self.policy
2390    }
2391    /// Returns the parameter-bank layout.
2392    pub const fn layout(&self) -> &GatedProductGroupLayout {
2393        &self.layout
2394    }
2395    /// Validates positive geometry and exact independent-group cardinality.
2396    pub fn validate(&self) -> Result<(), Error> {
2397        for (name, value) in [
2398            ("group_count", self.group_count),
2399            ("input_dimensions", self.input_dimensions),
2400            ("intermediate_dimensions", self.intermediate_dimensions),
2401            ("output_dimensions", self.output_dimensions),
2402        ] {
2403            if value <= 0 {
2404                return Err(Error::backend(format!(
2405                    "gated-product group-bank {name} must be positive, got {value}"
2406                )));
2407            }
2408        }
2409        self.policy.validate()?;
2410        if let GatedProductGroupLayout::Independent(groups) = &self.layout {
2411            let expected = usize::try_from(self.group_count).map_err(Error::backend)?;
2412            if groups.len() != expected {
2413                return Err(Error::backend(format!(
2414                    "independent gated-product bank has {} groups, expected {expected}",
2415                    groups.len()
2416                )));
2417            }
2418        }
2419        let projections = match &self.layout {
2420            GatedProductGroupLayout::Packed { gate_up, down } => vec![gate_up, down],
2421            GatedProductGroupLayout::Independent(groups) => groups
2422                .iter()
2423                .flat_map(|group| [&group.gate, &group.up, &group.down])
2424                .collect(),
2425        };
2426        let mut identities = std::collections::BTreeSet::new();
2427        for projection in projections {
2428            projection.validate()?;
2429            for parameter in projection.parameters() {
2430                let identity = &parameter.id;
2431                if !identities.insert(identity) {
2432                    return Err(Error::backend(format!(
2433                        "gated-product group parameter identity {identity} is duplicated"
2434                    )));
2435                }
2436            }
2437        }
2438        Ok(())
2439    }
2440}
2441
2442/// Rank-local grouped output split around the tensor-parallel reduction.
2443#[derive(Debug, Clone)]
2444pub struct TensorParallelGroupedOutput<T> {
2445    /// Rank-local projection contribution to all-sum.
2446    reducible: T,
2447    /// Replicated selection-weighted down bias added once after all-sum.
2448    post_reduce: Option<T>,
2449}
2450
2451impl<T> TensorParallelGroupedOutput<T> {
2452    /// Creates one rank-local grouped projection output.
2453    pub fn new(reducible: T, post_reduce: Option<T>) -> Self {
2454        Self {
2455            reducible,
2456            post_reduce,
2457        }
2458    }
2459    /// Returns the rank-local reducible tensor.
2460    pub const fn reducible(&self) -> &T {
2461        &self.reducible
2462    }
2463    /// Returns the optional post-reduction term.
2464    pub const fn post_reduce(&self) -> Option<&T> {
2465        self.post_reduce.as_ref()
2466    }
2467    /// Consumes the output into its reduction contribution and post-reduction term.
2468    pub fn into_parts(self) -> (T, Option<T>) {
2469        (self.reducible, self.post_reduce)
2470    }
2471}
2472
2473/// Statically dispatched grouped gated-product bank.
2474pub trait GroupedGatedProductOperator<T: Tensor>: Clone + Debug + Parameterized<T> {
2475    /// Returns the architecture-owned construction specification used by this bank.
2476    ///
2477    /// Runtime providers use this metadata when they substitute cached or remote
2478    /// parameters for the resident bank, so every execution path retains the
2479    /// same geometry, encoding, bias, and activation policy.
2480    fn spec(&self) -> &GroupedGatedProductSpec;
2481
2482    /// Executes selected groups and combines their outputs by selection weight.
2483    fn forward_grouped(
2484        &mut self,
2485        input: &T,
2486        selections: &GroupSelection<T>,
2487        context: &T::Context,
2488    ) -> Result<T, Error>;
2489}
2490
2491/// Additive mechanism for tensor-parallel grouped gated-product partials.
2492pub trait TensorParallelGroupedGatedProductOperator<T: Tensor>:
2493    GroupedGatedProductOperator<T>
2494{
2495    /// Executes a rank-local partial and separates replicated down bias for one
2496    /// literal post-reduction addition.
2497    fn forward_grouped_tensor_parallel(
2498        &mut self,
2499        input: &T,
2500        selections: &GroupSelection<T>,
2501        partitions: usize,
2502        context: &T::Context,
2503    ) -> Result<TensorParallelGroupedOutput<T>, Error>;
2504}
2505
2506/// Complete construction specification for packed grouped ReLU-squared groups.
2507#[derive(Debug, Clone)]
2508pub struct GroupedRelu2Spec {
2509    /// Number of groups.
2510    group_count: i32,
2511    /// Input and output hidden width.
2512    hidden_dimensions: i32,
2513    /// Per-group intermediate width.
2514    intermediate_dimensions: i32,
2515    /// Packed up-projection identity and physical format.
2516    up: GroupedProjectionSpec,
2517    /// Packed down-projection identity and physical format.
2518    down: GroupedProjectionSpec,
2519}
2520
2521impl GroupedRelu2Spec {
2522    /// Creates one validated grouped ReLU-squared mechanism request.
2523    pub fn new(
2524        group_count: i32,
2525        hidden_dimensions: i32,
2526        intermediate_dimensions: i32,
2527        up: GroupedProjectionSpec,
2528        down: GroupedProjectionSpec,
2529    ) -> Result<Self, Error> {
2530        let spec = Self {
2531            group_count,
2532            hidden_dimensions,
2533            intermediate_dimensions,
2534            up,
2535            down,
2536        };
2537        spec.validate()?;
2538        Ok(spec)
2539    }
2540    /// Returns the number of parameter groups.
2541    pub const fn group_count(&self) -> i32 {
2542        self.group_count
2543    }
2544    /// Returns the input and output width.
2545    pub const fn hidden_dimensions(&self) -> i32 {
2546        self.hidden_dimensions
2547    }
2548    /// Returns the per-group intermediate width.
2549    pub const fn intermediate_dimensions(&self) -> i32 {
2550        self.intermediate_dimensions
2551    }
2552    /// Returns the up projection.
2553    pub const fn up(&self) -> &GroupedProjectionSpec {
2554        &self.up
2555    }
2556    /// Returns the down projection.
2557    pub const fn down(&self) -> &GroupedProjectionSpec {
2558        &self.down
2559    }
2560    /// Validates positive geometry.
2561    pub fn validate(&self) -> Result<(), Error> {
2562        if self.group_count <= 0 || self.hidden_dimensions <= 0 || self.intermediate_dimensions <= 0
2563        {
2564            return Err(Error::backend("invalid ReLU2 group-bank geometry"));
2565        }
2566        self.up.validate()?;
2567        self.down.validate()?;
2568        let mut identities = std::collections::BTreeSet::new();
2569        for projection in [&self.up, &self.down] {
2570            for parameter in projection.parameters() {
2571                if !identities.insert(&parameter.id) {
2572                    return Err(Error::backend(format!(
2573                        "ReLU2 group parameter identity {} is duplicated",
2574                        parameter.id
2575                    )));
2576                }
2577            }
2578        }
2579        Ok(())
2580    }
2581}
2582
2583/// Statically dispatched grouped ReLU-squared bank.
2584pub trait GroupedRelu2Operator<T: Tensor>: Clone + Debug + Parameterized<T> {
2585    /// Executes selected groups and combines their outputs by selection weight.
2586    fn forward_grouped(
2587        &mut self,
2588        input: &T,
2589        selections: &GroupSelection<T>,
2590        context: &T::Context,
2591    ) -> Result<T, Error>;
2592}
2593
2594/// Additive mechanism for tensor-parallel grouped ReLU-squared partials.
2595pub trait TensorParallelGroupedRelu2Operator<T: Tensor>: GroupedRelu2Operator<T> {
2596    /// Executes a rank-local partial and separates replicated down bias for one
2597    /// literal post-reduction addition.
2598    fn forward_grouped_tensor_parallel(
2599        &mut self,
2600        input: &T,
2601        selections: &GroupSelection<T>,
2602        partitions: usize,
2603        context: &T::Context,
2604    ) -> Result<TensorParallelGroupedOutput<T>, Error>;
2605}
2606
2607/// Neural backend extension for grouped computation.
2608pub trait GroupedNeuralBackend: NeuralBackend {
2609    /// Concrete top-k selector.
2610    type Selector: GroupSelectionOperator<Self::Tensor>;
2611    /// Concrete packed or independently materialized gated-product bank.
2612    type GatedProductGroups: GroupedGatedProductOperator<Self::Tensor>;
2613    /// Concrete packed or independently materialized ReLU-squared bank.
2614    type Relu2Groups: GroupedRelu2Operator<Self::Tensor>;
2615
2616    /// Applies one packed block-diagonal projection independently across an
2617    /// explicit group axis.
2618    fn grouped_linear(
2619        linear: &mut Self::Linear,
2620        input: &Self::Tensor,
2621        groups: i32,
2622        output_per_group: i32,
2623        context: &<Self::Tensor as Tensor>::Context,
2624    ) -> Result<Self::Tensor, Error>;
2625
2626    /// Builds a selector with architecture-selected top-k semantics.
2627    fn top_k_group_selector(
2628        spec: TopKGroupSelectorSpec,
2629        context: &<Self::Tensor as Tensor>::Context,
2630    ) -> Result<Self::Selector, Error>;
2631
2632    /// Builds a grouped gated-product bank.
2633    fn grouped_gated_product(
2634        spec: GroupedGatedProductSpec,
2635        context: &<Self::Tensor as Tensor>::Context,
2636    ) -> Result<Self::GatedProductGroups, Error>;
2637
2638    /// Builds a grouped ReLU-squared bank.
2639    fn grouped_relu2(
2640        spec: GroupedRelu2Spec,
2641        context: &<Self::Tensor as Tensor>::Context,
2642    ) -> Result<Self::Relu2Groups, Error>;
2643
2644    /// Selects primary groups and jointly normalizes always-on group coefficients.
2645    fn joint_group_selection(
2646        input: JointGroupSelectionInput<'_, Self::Tensor>,
2647        context: &<Self::Tensor as Tensor>::Context,
2648    ) -> Result<JointGroupSelection<Self::Tensor>, Error>;
2649}
2650
2651/// Grouped backend whose selected realization provides every required
2652/// tensor-parallel grouped partial operation.
2653pub trait TensorParallelGroupedNeuralBackend: GroupedNeuralBackend {
2654    /// Executes one rank-local gated-product grouped partial.
2655    fn gated_product_groups_tensor_parallel(
2656        groups: &mut Self::GatedProductGroups,
2657        input: &Self::Tensor,
2658        selections: &GroupSelection<Self::Tensor>,
2659        partitions: usize,
2660        context: &<Self::Tensor as Tensor>::Context,
2661    ) -> Result<TensorParallelGroupedOutput<Self::Tensor>, Error>;
2662
2663    /// Executes one rank-local ReLU-squared grouped partial.
2664    fn relu2_groups_tensor_parallel(
2665        groups: &mut Self::Relu2Groups,
2666        input: &Self::Tensor,
2667        selections: &GroupSelection<Self::Tensor>,
2668        partitions: usize,
2669        context: &<Self::Tensor as Tensor>::Context,
2670    ) -> Result<TensorParallelGroupedOutput<Self::Tensor>, Error>;
2671}
2672
2673impl<B> TensorParallelGroupedNeuralBackend for B
2674where
2675    B: GroupedNeuralBackend,
2676    B::GatedProductGroups: TensorParallelGroupedGatedProductOperator<B::Tensor>,
2677    B::Relu2Groups: TensorParallelGroupedRelu2Operator<B::Tensor>,
2678{
2679    fn gated_product_groups_tensor_parallel(
2680        groups: &mut Self::GatedProductGroups,
2681        input: &Self::Tensor,
2682        selections: &GroupSelection<Self::Tensor>,
2683        partitions: usize,
2684        context: &<Self::Tensor as Tensor>::Context,
2685    ) -> Result<TensorParallelGroupedOutput<Self::Tensor>, Error> {
2686        groups.forward_grouped_tensor_parallel(input, selections, partitions, context)
2687    }
2688
2689    fn relu2_groups_tensor_parallel(
2690        groups: &mut Self::Relu2Groups,
2691        input: &Self::Tensor,
2692        selections: &GroupSelection<Self::Tensor>,
2693        partitions: usize,
2694        context: &<Self::Tensor as Tensor>::Context,
2695    ) -> Result<TensorParallelGroupedOutput<Self::Tensor>, Error> {
2696        groups.forward_grouped_tensor_parallel(input, selections, partitions, context)
2697    }
2698}
2699
2700/// Complete construction specification for one multi-stream residual mix.
2701#[derive(Debug, Clone)]
2702pub struct HyperConnectionSpec {
2703    /// Number of parallel residual streams.
2704    pub streams: i32,
2705    /// Hidden width of each stream.
2706    pub hidden_size: i32,
2707    /// Sinkhorn row/column normalization passes.
2708    pub sinkhorn_iterations: usize,
2709    /// Positive numerical epsilon used by Sinkhorn normalization.
2710    pub epsilon: f32,
2711    /// Projection producing pre, post, and stream-mixing logits.
2712    pub function: ParameterSpec,
2713    /// Additive base for the mixing logits.
2714    pub base: ParameterSpec,
2715    /// Learned scales for pre, post, and matrix logits.
2716    pub scale: ParameterSpec,
2717}
2718
2719impl HyperConnectionSpec {
2720    /// Validates geometry and numerical policy without inspecting parameters.
2721    pub fn validate(&self) -> Result<(), Error> {
2722        if self.streams <= 0 || self.hidden_size <= 0 {
2723            return Err(Error::backend(
2724                "hyper-connection streams and hidden size must be positive",
2725            ));
2726        }
2727        if self.sinkhorn_iterations == 0 {
2728            return Err(Error::backend(
2729                "hyper-connection Sinkhorn iteration count must be positive",
2730            ));
2731        }
2732        if !self.epsilon.is_finite() || self.epsilon <= 0.0 {
2733            return Err(Error::backend(
2734                "hyper-connection epsilon must be finite and positive",
2735            ));
2736        }
2737        Ok(())
2738    }
2739}
2740
2741/// Complete construction specification for the final stream collapse.
2742#[derive(Debug, Clone)]
2743pub struct HyperHeadSpec {
2744    /// Number of parallel residual streams.
2745    pub streams: i32,
2746    /// Hidden width of each stream.
2747    pub hidden_size: i32,
2748    /// RMS preparation epsilon.
2749    pub norm_epsilon: f32,
2750    /// Positive offset added to collapse coefficients.
2751    pub epsilon: f32,
2752    /// Projection producing per-stream collapse logits.
2753    pub function: ParameterSpec,
2754    /// Additive base for collapse logits.
2755    pub base: ParameterSpec,
2756    /// Learned collapse-logit scale.
2757    pub scale: ParameterSpec,
2758}
2759
2760impl HyperHeadSpec {
2761    /// Validates geometry and numerical policy without inspecting parameters.
2762    pub fn validate(&self) -> Result<(), Error> {
2763        if self.streams <= 0 || self.hidden_size <= 0 {
2764            return Err(Error::backend(
2765                "hyper-head streams and hidden size must be positive",
2766            ));
2767        }
2768        if !self.norm_epsilon.is_finite()
2769            || self.norm_epsilon <= 0.0
2770            || !self.epsilon.is_finite()
2771            || self.epsilon <= 0.0
2772        {
2773            return Err(Error::backend(
2774                "hyper-head epsilons must be finite and positive",
2775            ));
2776        }
2777        Ok(())
2778    }
2779}
2780
2781/// Coefficients and collapsed value produced before one residual sublayer.
2782#[derive(Debug, Clone)]
2783pub struct HyperConnectionState<T> {
2784    /// Residual streams reduced to one sublayer input.
2785    pub collapsed: T,
2786    /// Coefficients used for the pre-sublayer reduction.
2787    pub pre: T,
2788    /// Coefficients used to inject the sublayer result into each stream.
2789    pub post: T,
2790    /// Doubly-stochastic stream-mixing matrix.
2791    pub combination: T,
2792}
2793
2794/// Backend-native operator for one hyper-connected residual cycle.
2795pub trait HyperConnectionOperator<T: Tensor>: Clone + Debug + Parameterized<T> {
2796    /// Performs FP32 RMS preparation, predicts mixing coefficients, applies
2797    /// Sinkhorn normalization, and collapses streams for a sublayer.
2798    fn collapse(
2799        &mut self,
2800        residual: &T,
2801        norm_epsilon: f32,
2802        context: &T::Context,
2803    ) -> Result<HyperConnectionState<T>, Error>;
2804
2805    /// Injects a sublayer result and mixes the previous residual streams.
2806    fn expand(
2807        &mut self,
2808        sublayer: &T,
2809        residual: &T,
2810        state: &HyperConnectionState<T>,
2811        context: &T::Context,
2812    ) -> Result<T, Error>;
2813}
2814
2815/// Backend-native operator for the final learned stream collapse.
2816pub trait HyperHeadOperator<T: Tensor>: Clone + Debug + Parameterized<T> {
2817    /// Collapses `[batch, tokens, streams, hidden]` into one hidden state.
2818    fn forward(&mut self, residual: &T, context: &T::Context) -> Result<T, Error>;
2819}
2820
2821/// Neural backend extension for hyper-connected residual architectures.
2822pub trait HyperNeuralBackend: NeuralBackend {
2823    /// Concrete multi-stream residual operator.
2824    type HyperConnection: HyperConnectionOperator<Self::Tensor>;
2825    /// Concrete final stream-collapse operator.
2826    type HyperHead: HyperHeadOperator<Self::Tensor>;
2827
2828    /// Builds one unloaded hyper-connection operator.
2829    fn hyper_connection(
2830        spec: HyperConnectionSpec,
2831        context: &<Self::Tensor as Tensor>::Context,
2832    ) -> Result<Self::HyperConnection, Error>;
2833
2834    /// Builds one unloaded final hyper-head operator.
2835    fn hyper_head(
2836        spec: HyperHeadSpec,
2837        context: &<Self::Tensor as Tensor>::Context,
2838    ) -> Result<Self::HyperHead, Error>;
2839}
2840
2841/// Neutral, statically dispatched multi-stream residual layer.
2842#[derive(Debug, Clone, Parameterized)]
2843#[parameterized(tensor = "B::Tensor")]
2844pub struct HyperConnection<B: HyperNeuralBackend> {
2845    operator: B::HyperConnection,
2846}
2847
2848impl<B: HyperNeuralBackend> HyperConnection<B> {
2849    /// Builds the backend operator from architecture-owned parameter slots.
2850    pub fn new(
2851        spec: HyperConnectionSpec,
2852        context: &<B::Tensor as Tensor>::Context,
2853    ) -> Result<Self, Error> {
2854        spec.validate()?;
2855        Ok(Self {
2856            operator: B::hyper_connection(spec, context)?,
2857        })
2858    }
2859
2860    /// Collapses residual streams for one sublayer.
2861    pub fn collapse(
2862        &mut self,
2863        residual: &B::Tensor,
2864        norm_epsilon: f32,
2865        context: &<B::Tensor as Tensor>::Context,
2866    ) -> Result<HyperConnectionState<B::Tensor>, Error> {
2867        self.operator.collapse(residual, norm_epsilon, context)
2868    }
2869
2870    /// Expands a sublayer result back into residual streams.
2871    pub fn expand(
2872        &mut self,
2873        sublayer: &B::Tensor,
2874        residual: &B::Tensor,
2875        state: &HyperConnectionState<B::Tensor>,
2876        context: &<B::Tensor as Tensor>::Context,
2877    ) -> Result<B::Tensor, Error> {
2878        self.operator.expand(sublayer, residual, state, context)
2879    }
2880}
2881
2882/// Neutral, statically dispatched final stream-collapse layer.
2883#[derive(Debug, Clone, Parameterized)]
2884#[parameterized(tensor = "B::Tensor")]
2885pub struct HyperHead<B: HyperNeuralBackend> {
2886    operator: B::HyperHead,
2887}
2888
2889impl<B: HyperNeuralBackend> HyperHead<B> {
2890    /// Builds the backend operator from architecture-owned parameter slots.
2891    pub fn new(
2892        spec: HyperHeadSpec,
2893        context: &<B::Tensor as Tensor>::Context,
2894    ) -> Result<Self, Error> {
2895        spec.validate()?;
2896        Ok(Self {
2897            operator: B::hyper_head(spec, context)?,
2898        })
2899    }
2900
2901    /// Collapses all residual streams to one hidden state.
2902    pub fn forward(
2903        &mut self,
2904        residual: &B::Tensor,
2905        context: &<B::Tensor as Tensor>::Context,
2906    ) -> Result<B::Tensor, Error> {
2907        self.operator.forward(residual, context)
2908    }
2909}
2910
2911/// One backend-native scaled-dot-product attention request.
2912///
2913/// Projected queries, keys, and values remain owned backend tensors so cached,
2914/// uncached, paged, and sliding implementations can consume the same request
2915/// without cloning or host materialization. Masks and learned per-query-head
2916/// sink logits are borrowed architecture state.
2917#[derive(Debug)]
2918pub struct AttentionRequest<'a, T> {
2919    /// Queries shaped `[batch, query_heads, query_tokens, head_dimensions]`.
2920    pub queries: T,
2921    /// Keys shaped `[batch, key_value_heads, key_tokens, head_dimensions]`.
2922    pub keys: T,
2923    /// Values shaped `[batch, key_value_heads, key_tokens, value_dimensions]`.
2924    pub values: T,
2925    /// Positive finite score scale.
2926    pub scale: f32,
2927    /// Optional additive or boolean attention mask.
2928    pub mask: Option<&'a T>,
2929    /// Optional learned sink logit for every query head.
2930    pub sinks: Option<&'a T>,
2931}
2932
2933impl<T: Tensor> AttentionRequest<'_, T> {
2934    /// Validates common grouped-query and sink geometry without inspecting values.
2935    pub fn validate(&self) -> Result<(), Error> {
2936        let queries = self.queries.shape();
2937        let keys = self.keys.shape();
2938        let values = self.values.shape();
2939        if queries.len() != 4
2940            || keys.len() != 4
2941            || values.len() != 4
2942            || queries[0] != keys[0]
2943            || keys[..3] != values[..3]
2944            || queries[3] != keys[3]
2945            || queries[1] <= 0
2946            || keys[1] <= 0
2947            || queries[1] % keys[1] != 0
2948            || queries[2] <= 0
2949            || keys[2] <= 0
2950            || values[3] <= 0
2951            || !self.scale.is_finite()
2952            || self.scale <= 0.0
2953        {
2954            return Err(Error::backend(format!(
2955                "invalid attention request geometry queries={queries:?} keys={keys:?} values={values:?} scale={}",
2956                self.scale
2957            )));
2958        }
2959        if let Some(sinks) = self.sinks {
2960            if sinks.shape() != [queries[1]] {
2961                return Err(Error::backend(format!(
2962                    "attention sinks require shape [{}], got {:?}",
2963                    queries[1],
2964                    sinks.shape()
2965                )));
2966            }
2967        }
2968        Ok(())
2969    }
2970}
2971
2972/// Backend-native key/value cache operations required by attention.
2973pub trait AttentionCache<T: Tensor> {
2974    /// Current absolute sequence offset.
2975    fn offset(&self) -> i32;
2976    /// Maximum retained history for a sliding cache.
2977    fn max_size(&self) -> Option<i32>;
2978    /// Appends projected keys and values and returns the tensors used by attention.
2979    fn update_for_attention(
2980        &mut self,
2981        keys: T,
2982        values: T,
2983        context: &T::Context,
2984    ) -> Result<(T, T), Error>;
2985    /// Runs cache-aware attention, including paged or quantized kernels where applicable.
2986    fn attention(
2987        &mut self,
2988        request: AttentionRequest<'_, T>,
2989        context: &T::Context,
2990    ) -> Result<T, Error>;
2991}
2992
2993/// Attention cache with a fixed set of bounded causal-convolution histories.
2994///
2995/// Slot identity is architecture policy; storage, persistence, and device
2996/// realization remain runtime/backend concerns.
2997pub trait AuxiliaryConvolutionState<T: Tensor>: AttentionCache<T> {
2998    /// Borrows one bounded convolution-history slot.
2999    fn convolution_state(&mut self, slot: u32) -> Result<&mut Option<T>, Error>;
3000}
3001
3002/// Semantic latent and rotary components retained by compressed attention.
3003#[derive(Debug, Clone)]
3004pub struct CompressedAttentionState<T> {
3005    /// Head-independent latent key/value representation.
3006    pub latent: T,
3007    /// Rotary-key representation aligned to the same token range.
3008    pub rotary: T,
3009}
3010
3011/// Result of appending compressed state.
3012#[derive(Debug, Clone)]
3013pub enum CompressedAttentionView<T> {
3014    /// Complete resident state used directly by prefill or decode attention.
3015    Resident(CompressedAttentionState<T>),
3016    /// Paged state; the appended components are retained for diagnostics while
3017    /// attention scans semantic blocks through the cache operator.
3018    Paged {
3019        /// Components appended by this update.
3020        appended: CompressedAttentionState<T>,
3021    },
3022}
3023
3024impl<T> CompressedAttentionView<T> {
3025    /// Returns the resident state when paging is not active.
3026    pub const fn resident(&self) -> Option<&CompressedAttentionState<T>> {
3027        match self {
3028            Self::Resident(state) => Some(state),
3029            Self::Paged { .. } => None,
3030        }
3031    }
3032
3033    /// Returns components suitable for non-materializing observation.
3034    pub const fn observable(&self) -> &CompressedAttentionState<T> {
3035        match self {
3036            Self::Resident(state) | Self::Paged { appended: state } => state,
3037        }
3038    }
3039
3040    /// Returns whether block-addressable paging is active.
3041    pub const fn is_paged(&self) -> bool {
3042        matches!(self, Self::Paged { .. })
3043    }
3044}
3045
3046/// One absolute token range supplied to blockwise compressed attention.
3047#[derive(Debug, Clone)]
3048pub struct CompressedAttentionBlock<T> {
3049    /// Inclusive absolute token start.
3050    pub start: i64,
3051    /// Exclusive absolute token end.
3052    pub end: i64,
3053    /// Latent and rotary components for this range.
3054    pub state: CompressedAttentionState<T>,
3055}
3056
3057/// Aggregate observations from one paged blockwise attention scan.
3058#[derive(Debug, Clone, Copy, Default, Eq, PartialEq)]
3059pub struct CompressedAttentionScan {
3060    /// Number of scanned sealed and tail blocks.
3061    pub blocks: u64,
3062    /// Number of persistent compressed bytes scanned.
3063    pub bytes: u64,
3064    /// Maximum caller-reported transient reconstruction bytes.
3065    pub reconstruction_scratch_bytes: u64,
3066}
3067
3068/// Fixed request metadata for an online blockwise attention recurrence.
3069#[derive(Debug, Clone, Copy)]
3070pub struct BlockwiseAttentionSpec<'a, T> {
3071    /// Queries shaped `[batch, heads, query_tokens, dimensions]`.
3072    pub queries: &'a T,
3073    /// Query/key score multiplier.
3074    pub scale: f32,
3075    /// Optional complete-context mask sliced by the backend for each block.
3076    pub mask: Option<&'a T>,
3077    /// Absolute position of the first query.
3078    pub query_start: i64,
3079    /// Absolute exclusive end of the complete visible context.
3080    pub context_end: i64,
3081    /// Optional causal sliding-window width.
3082    pub sliding_window: Option<i32>,
3083    /// Number of prefix tokens visible outside a sliding window.
3084    pub prefix_tokens: i64,
3085    /// Optional learned per-head sink logits.
3086    pub sinks: Option<&'a T>,
3087}
3088
3089/// Typed backend fusion for exact online softmax across ordered attention
3090/// blocks. Persistent caches remain compressed; only one reconstructed block
3091/// is live at a time.
3092pub trait BlockwiseAttentionBackend: NeuralBackend {
3093    /// Backend-native running maximum, normalization, and value accumulator.
3094    type BlockwiseAccumulator;
3095
3096    /// Starts an empty online attention recurrence.
3097    fn begin_blockwise_attention(
3098        spec: BlockwiseAttentionSpec<'_, Self::Tensor>,
3099        context: &<Self::Tensor as Tensor>::Context,
3100    ) -> Result<Self::BlockwiseAccumulator, Error>;
3101
3102    /// Incorporates one absolute key/value block and reports transient bytes.
3103    fn accumulate_blockwise_attention(
3104        accumulator: &mut Self::BlockwiseAccumulator,
3105        start: i64,
3106        end: i64,
3107        keys: Self::Tensor,
3108        values: Self::Tensor,
3109        context: &<Self::Tensor as Tensor>::Context,
3110    ) -> Result<u64, Error>;
3111
3112    /// Finishes the online recurrence.
3113    fn finish_blockwise_attention(
3114        accumulator: Self::BlockwiseAccumulator,
3115        context: &<Self::Tensor as Tensor>::Context,
3116    ) -> Result<Self::Tensor, Error>;
3117}
3118
3119/// Backend-owned compressed-attention state over semantic components.
3120///
3121/// Checkpoints are cheap backend snapshots used by speculative fork/rollback.
3122/// `finalize` seals mutable state before runtime prompt-cache persistence.
3123pub trait CompressedAttentionCache<T: Tensor>: Debug {
3124    /// Backend snapshot preserving paging and residency identity.
3125    type Checkpoint: Clone + Debug;
3126
3127    /// Current absolute token frontier.
3128    fn offset(&self) -> i32;
3129    /// Whether state is block-addressable rather than fully resident.
3130    fn is_paged(&self) -> bool;
3131    /// Appends aligned latent and rotary components.
3132    fn append(
3133        &mut self,
3134        state: CompressedAttentionState<T>,
3135        context: &T::Context,
3136    ) -> Result<CompressedAttentionView<T>, Error>;
3137    /// Visits all paged blocks in absolute order without host tensor
3138    /// materialization. The callback reports its transient scratch usage.
3139    fn visit_blocks<F>(
3140        &mut self,
3141        query_tokens: i32,
3142        context: &T::Context,
3143        visitor: F,
3144    ) -> Result<CompressedAttentionScan, Error>
3145    where
3146        F: FnMut(CompressedAttentionBlock<T>) -> Result<u64, Error>;
3147    /// Captures a cheap speculative checkpoint.
3148    fn checkpoint(&self) -> Self::Checkpoint;
3149    /// Restores a previous checkpoint, removing any later paged state.
3150    fn restore(&mut self, checkpoint: &Self::Checkpoint, context: &T::Context)
3151        -> Result<(), Error>;
3152    /// Seals mutable tails before prompt-cache snapshot persistence.
3153    fn finalize(&mut self) -> Result<(), Error>;
3154    /// Clears all resident or paged state.
3155    fn clear(&mut self) -> Result<(), Error>;
3156}
3157
3158/// Complete source windows emitted by an append-only gated-pooling stream.
3159#[derive(Debug, Clone)]
3160pub struct PoolingWindows<T> {
3161    /// Source values shaped `[batch, complete_source_tokens, width]`.
3162    pub values: T,
3163    /// Gate logits aligned to `values`.
3164    pub gates: T,
3165    /// Absolute source-token position of the first returned value.
3166    pub base_position: i32,
3167}
3168
3169/// Previous overlap carried between adjacent gated-pooling windows.
3170#[derive(Debug, Clone)]
3171pub struct PoolingOverlap<T> {
3172    /// Previous-window source values, when a complete predecessor exists.
3173    pub values: Option<T>,
3174    /// Previous-window gate logits aligned to `values`.
3175    pub gates: Option<T>,
3176}
3177
3178/// Backend-owned local-key and compressed-pooling state used by architectures
3179/// that mix bounded local attention with append-only pooled history.
3180///
3181/// Stream ordinals are architecture-owned. Implementations preserve pending,
3182/// pooled, and overlap components through checkpoint, rollback, prompt-cache,
3183/// resident, and paged realizations without exposing storage classes here.
3184pub trait PoolingAttentionCache<T: Tensor>: Debug {
3185    /// Backend snapshot preserving all local and pooling state.
3186    type Checkpoint: Clone + Debug;
3187
3188    /// Current absolute source-token frontier.
3189    fn offset(&self) -> i32;
3190    /// Returns the configured source-token ratio for one pooling stream.
3191    fn pooling_ratio(&self, stream: u32) -> Option<i32>;
3192    /// Appends local keys and returns the bounded history used by attention,
3193    /// shaped `[batch, local_tokens, dimensions]`.
3194    fn append_local(&mut self, keys: T, context: &T::Context) -> Result<T, Error>;
3195    /// Builds causal eligibility for the currently retained local history.
3196    fn local_mask(&self, query_tokens: i32, offset: i32, context: &T::Context) -> Result<T, Error>;
3197    /// Accumulates source values and gates, retaining any incomplete suffix.
3198    fn accumulate_pooling_windows(
3199        &mut self,
3200        stream: u32,
3201        values: T,
3202        gates: T,
3203        absolute_offset: i32,
3204        context: &T::Context,
3205    ) -> Result<PoolingWindows<T>, Error>;
3206    /// Replaces overlap carried by one stream and returns its previous pair.
3207    fn replace_pooling_overlap(
3208        &mut self,
3209        stream: u32,
3210        values: T,
3211        gates: T,
3212    ) -> Result<PoolingOverlap<T>, Error>;
3213    /// Appends newly pooled values and returns the complete pooled history.
3214    fn append_pooled(&mut self, stream: u32, values: T, context: &T::Context) -> Result<T, Error>;
3215    /// Builds causal eligibility for one stream's complete pooled history.
3216    fn pooling_mask(
3217        &self,
3218        stream: u32,
3219        query_tokens: i32,
3220        offset: i32,
3221        context: &T::Context,
3222    ) -> Result<Option<T>, Error>;
3223    /// Captures a cheap speculative checkpoint.
3224    fn checkpoint(&self) -> Self::Checkpoint;
3225    /// Restores a previous checkpoint.
3226    fn restore(&mut self, checkpoint: &Self::Checkpoint, context: &T::Context)
3227        -> Result<(), Error>;
3228    /// Seals mutable local and pooled tails before persistence.
3229    fn finalize(&mut self) -> Result<(), Error>;
3230    /// Clears all local and pooling state.
3231    fn clear(&mut self) -> Result<(), Error>;
3232}
3233
3234/// Explicit optional operations a backend promises to execute.
3235///
3236/// These capabilities cover explicitly admitted methods on [`NeuralBackend`]
3237/// and every optional [`Tensor`] method whose default implementation fails
3238/// closed. Architectures validate their required set while constructing
3239/// modules, before parameters are loaded or a forward pass can begin.
3240#[derive(Debug, Clone, Copy, Default, Eq, PartialEq)]
3241pub struct NeuralOperatorCapabilities(u64);
3242
3243impl NeuralOperatorCapabilities {
3244    /// No optional forward operators.
3245    pub const NONE: Self = Self(0);
3246    /// Tanh-approximated GELU.
3247    pub const GELU_APPROXIMATE: Self = Self(1 << 0);
3248    /// Logistic sigmoid.
3249    pub const SIGMOID: Self = Self(1 << 1);
3250    /// Softplus.
3251    pub const SOFTPLUS: Self = Self(1 << 2);
3252    /// Natural exponential.
3253    pub const EXP: Self = Self(1 << 3);
3254    /// Gated grouped RMS normalization.
3255    pub const GATED_GROUP_RMS_NORM: Self = Self(1 << 4);
3256    /// L2 normalization.
3257    pub const L2_NORMALIZE: Self = Self(1 << 5);
3258    /// SiLU-gated grouped RMS normalization.
3259    pub const SILU_GATED_GROUP_RMS_NORM: Self = Self(1 << 6);
3260    /// Segmented attention.
3261    pub const SEGMENTED_ATTENTION: Self = Self(1 << 7);
3262    /// Gated-delta recurrent scan.
3263    pub const GATED_DELTA_SCAN: Self = Self(1 << 8);
3264    /// Selective state-space scan.
3265    pub const SELECTIVE_STATE_SPACE_SCAN: Self = Self(1 << 9);
3266    /// Indexed sparse attention.
3267    pub const INDEXED_ATTENTION: Self = Self(1 << 10);
3268    /// Dense pooled attention.
3269    pub const POOLED_ATTENTION: Self = Self(1 << 11);
3270    /// Pooled-position selection.
3271    pub const POOLED_POSITION_SELECTION: Self = Self(1 << 12);
3272    /// Pooled-mask gathering.
3273    pub const POOLED_MASK_GATHER: Self = Self(1 << 13);
3274    /// Attention with learned sink logits.
3275    pub const ATTENTION_SINKS: Self = Self(1 << 14);
3276    /// Learned relative-profile attention.
3277    pub const RELATIVE_ATTENTION: Self = Self(1 << 15);
3278    /// Joint grouped/shared group selection.
3279    pub const JOINT_GROUP_SELECTION: Self = Self(1 << 16);
3280    /// Weightless RMS normalization.
3281    pub const RMS_NORM_WITHOUT_WEIGHT: Self = Self(1 << 17);
3282    /// Grouped block-diagonal linear projection.
3283    pub const GROUPED_LINEAR: Self = Self(1 << 18);
3284    /// Tensor-parallel sum reduction.
3285    pub const SUM_PARALLEL: Self = Self(1 << 19);
3286    /// Unloaded signed 32-bit integer parameter allocation.
3287    pub const UNLOADED_I32: Self = Self(1 << 20);
3288    /// Signed 32-bit integer tensor construction from host data.
3289    pub const FROM_I32_SLICE: Self = Self(1 << 21);
3290    /// Floating-point host materialization.
3291    pub const TO_F32_VEC: Self = Self(1 << 22);
3292    /// Signed 32-bit integer host materialization.
3293    pub const TO_I32_VEC: Self = Self(1 << 23);
3294    /// Floating-point filled tensor construction.
3295    pub const FULL_F32: Self = Self(1 << 24);
3296    /// Signed 32-bit integer filled tensor construction.
3297    pub const FULL_I32: Self = Self(1 << 25);
3298    /// Elementwise hyperbolic tangent.
3299    pub const TANH: Self = Self(1 << 26);
3300    /// Elementwise clamp with tensor bounds.
3301    pub const CLIP: Self = Self(1 << 27);
3302    /// Axis-wise softmax.
3303    pub const SOFTMAX_AXIS: Self = Self(1 << 28);
3304    /// Tensor broadcasting.
3305    pub const BROADCAST_TO: Self = Self(1 << 29);
3306    /// Dtype-preserving zero allocation.
3307    pub const ZEROS_LIKE: Self = Self(1 << 30);
3308    /// Signed integer scalar comparison.
3309    pub const EQUAL_I32: Self = Self(1 << 31);
3310    /// Elementwise logical disjunction.
3311    pub const LOGICAL_OR: Self = Self(1 << 32);
3312    /// Conditional tensor selection.
3313    pub const WHERE_CONDITION: Self = Self(1 << 33);
3314    /// Masked tensor scatter.
3315    pub const MASKED_SCATTER: Self = Self(1 << 34);
3316    /// Rotary positions with caller-supplied frequencies.
3317    pub const ROPE_WITH_FREQUENCIES: Self = Self(1 << 35);
3318    /// Two-dimensional convolution.
3319    pub const CONV2D: Self = Self(1 << 36);
3320    /// Multi-axis rotary embedding construction.
3321    pub const MULTI_AXIS_ROTARY_EMBEDDINGS: Self = Self(1 << 37);
3322    /// Masked vocabulary output projection.
3323    pub const MASKED_OUTPUT_PROJECTION: Self = Self(1 << 38);
3324    /// Every currently declared optional operation.
3325    pub const ALL: Self = Self((1 << 39) - 1);
3326
3327    /// Returns the union of two capability sets.
3328    pub const fn union(self, other: Self) -> Self {
3329        Self(self.0 | other.0)
3330    }
3331
3332    /// Returns whether this set contains every required capability.
3333    pub const fn contains(self, required: Self) -> bool {
3334        self.0 & required.0 == required.0
3335    }
3336
3337    /// Returns stable names for every required capability absent from this set.
3338    pub fn missing_capability_names(self, required: Self) -> Vec<&'static str> {
3339        const NAMES: &[(NeuralOperatorCapabilities, &str)] = &[
3340            (
3341                NeuralOperatorCapabilities::GELU_APPROXIMATE,
3342                "gelu_approximate",
3343            ),
3344            (NeuralOperatorCapabilities::SIGMOID, "sigmoid"),
3345            (NeuralOperatorCapabilities::SOFTPLUS, "softplus"),
3346            (NeuralOperatorCapabilities::EXP, "exp"),
3347            (
3348                NeuralOperatorCapabilities::GATED_GROUP_RMS_NORM,
3349                "gated_group_rms_norm",
3350            ),
3351            (NeuralOperatorCapabilities::L2_NORMALIZE, "l2_normalize"),
3352            (
3353                NeuralOperatorCapabilities::SILU_GATED_GROUP_RMS_NORM,
3354                "silu_gated_group_rms_norm",
3355            ),
3356            (
3357                NeuralOperatorCapabilities::SEGMENTED_ATTENTION,
3358                "segmented_attention",
3359            ),
3360            (
3361                NeuralOperatorCapabilities::GATED_DELTA_SCAN,
3362                "gated_delta_scan",
3363            ),
3364            (
3365                NeuralOperatorCapabilities::SELECTIVE_STATE_SPACE_SCAN,
3366                "selective_state_space_scan",
3367            ),
3368            (
3369                NeuralOperatorCapabilities::INDEXED_ATTENTION,
3370                "indexed_attention",
3371            ),
3372            (
3373                NeuralOperatorCapabilities::POOLED_ATTENTION,
3374                "pooled_attention",
3375            ),
3376            (
3377                NeuralOperatorCapabilities::POOLED_POSITION_SELECTION,
3378                "select_pooled_positions",
3379            ),
3380            (
3381                NeuralOperatorCapabilities::POOLED_MASK_GATHER,
3382                "gather_pooled_mask",
3383            ),
3384            (
3385                NeuralOperatorCapabilities::ATTENTION_SINKS,
3386                "attention_sinks",
3387            ),
3388            (
3389                NeuralOperatorCapabilities::RELATIVE_ATTENTION,
3390                "relative_attention",
3391            ),
3392            (
3393                NeuralOperatorCapabilities::JOINT_GROUP_SELECTION,
3394                "joint_group_selection",
3395            ),
3396            (
3397                NeuralOperatorCapabilities::RMS_NORM_WITHOUT_WEIGHT,
3398                "rms_norm_without_weight",
3399            ),
3400            (NeuralOperatorCapabilities::GROUPED_LINEAR, "grouped_linear"),
3401            (NeuralOperatorCapabilities::SUM_PARALLEL, "sum_parallel"),
3402            (NeuralOperatorCapabilities::UNLOADED_I32, "unloaded_i32"),
3403            (NeuralOperatorCapabilities::FROM_I32_SLICE, "from_i32_slice"),
3404            (NeuralOperatorCapabilities::TO_F32_VEC, "to_f32_vec"),
3405            (NeuralOperatorCapabilities::TO_I32_VEC, "to_i32_vec"),
3406            (NeuralOperatorCapabilities::FULL_F32, "full_f32"),
3407            (NeuralOperatorCapabilities::FULL_I32, "full_i32"),
3408            (NeuralOperatorCapabilities::TANH, "tanh"),
3409            (NeuralOperatorCapabilities::CLIP, "clip"),
3410            (NeuralOperatorCapabilities::SOFTMAX_AXIS, "softmax_axis"),
3411            (NeuralOperatorCapabilities::BROADCAST_TO, "broadcast_to"),
3412            (NeuralOperatorCapabilities::ZEROS_LIKE, "zeros_like"),
3413            (NeuralOperatorCapabilities::EQUAL_I32, "equal_i32"),
3414            (NeuralOperatorCapabilities::LOGICAL_OR, "logical_or"),
3415            (
3416                NeuralOperatorCapabilities::WHERE_CONDITION,
3417                "where_condition",
3418            ),
3419            (NeuralOperatorCapabilities::MASKED_SCATTER, "masked_scatter"),
3420            (
3421                NeuralOperatorCapabilities::ROPE_WITH_FREQUENCIES,
3422                "rope_with_frequencies",
3423            ),
3424            (NeuralOperatorCapabilities::CONV2D, "conv2d"),
3425            (
3426                NeuralOperatorCapabilities::MULTI_AXIS_ROTARY_EMBEDDINGS,
3427                "multi_axis_rotary_embeddings",
3428            ),
3429            (
3430                NeuralOperatorCapabilities::MASKED_OUTPUT_PROJECTION,
3431                "masked_output_projection",
3432            ),
3433        ];
3434        NAMES
3435            .iter()
3436            .filter_map(|(capability, name)| {
3437                (required.contains(*capability) && !self.contains(*capability)).then_some(*name)
3438            })
3439            .collect()
3440    }
3441}
3442
3443#[cfg(test)]
3444mod neural_operator_capability_tests {
3445    use super::NeuralOperatorCapabilities as C;
3446
3447    #[test]
3448    fn all_includes_every_fail_closed_tensor_operation() {
3449        for (capability, name) in [
3450            (C::UNLOADED_I32, "unloaded_i32"),
3451            (C::FROM_I32_SLICE, "from_i32_slice"),
3452            (C::TO_F32_VEC, "to_f32_vec"),
3453            (C::TO_I32_VEC, "to_i32_vec"),
3454            (C::FULL_F32, "full_f32"),
3455            (C::FULL_I32, "full_i32"),
3456            (C::TANH, "tanh"),
3457            (C::CLIP, "clip"),
3458            (C::SOFTMAX_AXIS, "softmax_axis"),
3459            (C::BROADCAST_TO, "broadcast_to"),
3460            (C::ZEROS_LIKE, "zeros_like"),
3461            (C::EQUAL_I32, "equal_i32"),
3462            (C::LOGICAL_OR, "logical_or"),
3463            (C::WHERE_CONDITION, "where_condition"),
3464            (C::MASKED_SCATTER, "masked_scatter"),
3465            (C::ROPE_WITH_FREQUENCIES, "rope_with_frequencies"),
3466            (C::CONV2D, "conv2d"),
3467            (
3468                C::MULTI_AXIS_ROTARY_EMBEDDINGS,
3469                "multi_axis_rotary_embeddings",
3470            ),
3471            (C::MASKED_OUTPUT_PROJECTION, "masked_output_projection"),
3472        ] {
3473            assert!(C::ALL.contains(capability));
3474            assert_eq!(C::NONE.missing_capability_names(capability), [name]);
3475        }
3476    }
3477}
3478
3479/// General neural-operator family selected by a shared architecture.
3480///
3481/// Associated concrete types make calls statically dispatched. Implementations
3482/// retain ownership of tensor storage, fusion, quantization, and collectives.
3483pub trait NeuralBackend: Sized + 'static {
3484    /// Optional forward operators explicitly supported by this backend.
3485    const OPERATOR_CAPABILITIES: NeuralOperatorCapabilities = NeuralOperatorCapabilities::NONE;
3486
3487    /// Backend tensor handle.
3488    type Tensor: Tensor;
3489    /// Native affine projection, including packed quantized variants.
3490    type Linear: LinearOperator<Self::Tensor>;
3491    /// Native embedding table.
3492    type Embedding: EmbeddingOperator<Self::Tensor>;
3493    /// Native normalization operator.
3494    type Normalization: NormalizationOperator<Self::Tensor>;
3495    /// Native rotary-position operator.
3496    type Rotary: RotaryOperator<Self::Tensor>;
3497    /// Backend collective context used by tensor-parallel execution.
3498    type ParallelContext: ?Sized;
3499
3500    /// Rejects an architecture before module construction when an optional
3501    /// forward operator is unavailable.
3502    fn require_operator_capabilities(
3503        architecture: &'static str,
3504        required: NeuralOperatorCapabilities,
3505    ) -> Result<(), Error> {
3506        let available = Self::OPERATOR_CAPABILITIES;
3507        if available.contains(required) {
3508            return Ok(());
3509        }
3510        Err(Error::backend(format!(
3511            "{architecture} requires unsupported backend operators: {}",
3512            available.missing_capability_names(required).join(", ")
3513        )))
3514    }
3515
3516    /// Builds one affine projection.
3517    fn linear(
3518        spec: LinearSpec,
3519        context: &<Self::Tensor as Tensor>::Context,
3520    ) -> Result<Self::Linear, Error>;
3521    /// Builds one token embedding table.
3522    fn embedding(
3523        spec: EmbeddingSpec,
3524        context: &<Self::Tensor as Tensor>::Context,
3525    ) -> Result<Self::Embedding, Error>;
3526    /// Builds an RMS normalization with an explicit scale policy.
3527    fn normalization(
3528        spec: NormalizationConstructionSpec,
3529        context: &<Self::Tensor as Tensor>::Context,
3530    ) -> Result<Self::Normalization, Error>;
3531    /// Builds the model's rotary-position operator.
3532    fn rotary(
3533        spec: RotarySpec,
3534        context: &<Self::Tensor as Tensor>::Context,
3535    ) -> Result<Self::Rotary, Error>;
3536    /// Applies SiLU using a backend-native implementation.
3537    fn silu(
3538        input: Self::Tensor,
3539        context: &<Self::Tensor as Tensor>::Context,
3540    ) -> Result<Self::Tensor, Error>;
3541    /// Applies the tanh-approximated GELU used by some encoder MLPs.
3542    fn gelu_approximate(
3543        input: Self::Tensor,
3544        context: &<Self::Tensor as Tensor>::Context,
3545    ) -> Result<Self::Tensor, Error> {
3546        let _ = (input, context);
3547        Err(Error::backend(
3548            "approximate GELU is not implemented by this backend",
3549        ))
3550    }
3551    /// Applies the logistic sigmoid elementwise.
3552    fn sigmoid(
3553        input: Self::Tensor,
3554        context: &<Self::Tensor as Tensor>::Context,
3555    ) -> Result<Self::Tensor, Error> {
3556        let _ = (input, context);
3557        Err(Error::backend("sigmoid is not implemented by this backend"))
3558    }
3559    /// Applies softplus elementwise.
3560    fn softplus(
3561        input: Self::Tensor,
3562        context: &<Self::Tensor as Tensor>::Context,
3563    ) -> Result<Self::Tensor, Error> {
3564        let _ = (input, context);
3565        Err(Error::backend(
3566            "softplus is not implemented by this backend",
3567        ))
3568    }
3569    /// Applies the natural exponential elementwise.
3570    fn exp(
3571        input: Self::Tensor,
3572        context: &<Self::Tensor as Tensor>::Context,
3573    ) -> Result<Self::Tensor, Error> {
3574        let _ = (input, context);
3575        Err(Error::backend(
3576            "exponential is not implemented by this backend",
3577        ))
3578    }
3579    /// Applies SiLU gating followed by grouped RMS normalization and scale.
3580    fn gated_group_rms_norm(
3581        input: &Self::Tensor,
3582        gate: &Self::Tensor,
3583        weight: &Self::Tensor,
3584        groups: i32,
3585        epsilon: f32,
3586        context: &<Self::Tensor as Tensor>::Context,
3587    ) -> Result<Self::Tensor, Error> {
3588        let _ = (input, gate, weight, groups, epsilon, context);
3589        Err(Error::backend(
3590            "gated grouped RMS normalization is not implemented by this backend",
3591        ))
3592    }
3593    /// Applies L2 normalization over the final axis.
3594    fn l2_normalize(
3595        input: &Self::Tensor,
3596        epsilon: f32,
3597        context: &<Self::Tensor as Tensor>::Context,
3598    ) -> Result<Self::Tensor, Error> {
3599        let _ = (input, epsilon, context);
3600        Err(Error::backend(
3601            "L2 normalization is not implemented by this backend",
3602        ))
3603    }
3604    /// Applies grouped RMS normalization to `input`, multiplies by a learned
3605    /// scale, and then modulates the result by `silu(gate)`.
3606    fn silu_gated_group_rms_norm(
3607        input: &Self::Tensor,
3608        gate: &Self::Tensor,
3609        weight: &Self::Tensor,
3610        groups: i32,
3611        epsilon: f32,
3612        context: &<Self::Tensor as Tensor>::Context,
3613    ) -> Result<Self::Tensor, Error> {
3614        let _ = (input, gate, weight, groups, epsilon, context);
3615        Err(Error::backend(
3616            "SiLU-gated grouped RMS normalization is not implemented by this backend",
3617        ))
3618    }
3619    /// Repeats a validated head axis using backend-native reshape and
3620    /// broadcast operations.
3621    fn expand_heads(
3622        input: &Self::Tensor,
3623        expansion: HeadExpansion,
3624        context: &<Self::Tensor as Tensor>::Context,
3625    ) -> Result<Self::Tensor, Error> {
3626        expansion.validate(input)?;
3627        if expansion.source_heads == expansion.target_heads {
3628            return Ok(input.clone());
3629        }
3630        let mut expanded_shape = input.shape().to_vec();
3631        expanded_shape.insert(expansion.axis + 1, 1);
3632        let expanded = input.reshape(&expanded_shape, context)?;
3633        expanded_shape[expansion.axis + 1] = expansion.repeats();
3634        let expanded = expanded.broadcast_to(&expanded_shape, context)?;
3635        expanded_shape[expansion.axis] = expansion.target_heads;
3636        expanded_shape.remove(expansion.axis + 1);
3637        expanded.reshape(&expanded_shape, context)
3638    }
3639    /// Runs unmasked attention independently over contiguous validated
3640    /// segments without exposing backend slicing to architecture code.
3641    fn segmented_attention(
3642        input: SegmentedAttentionInput<'_, Self::Tensor>,
3643        context: &<Self::Tensor as Tensor>::Context,
3644    ) -> Result<Self::Tensor, Error> {
3645        input.validate()?;
3646        let _ = context;
3647        Err(Error::backend(
3648            "segmented attention is not implemented by this backend",
3649        ))
3650    }
3651    /// Adds a residual branch, optionally retaining the accumulator in FP32.
3652    fn add_residual(
3653        residual: &Self::Tensor,
3654        branch: &Self::Tensor,
3655        fp32: bool,
3656        context: &<Self::Tensor as Tensor>::Context,
3657    ) -> Result<Self::Tensor, Error> {
3658        let _ = fp32;
3659        residual.add(branch, context)
3660    }
3661    /// Executes a gated-delta recurrent scan over projected head-major values.
3662    ///
3663    /// Inputs are `[batch, sequence, heads, dimensions]` except `beta`, which
3664    /// is `[batch, sequence, heads]`. The optional initial and returned state
3665    /// are `[batch, heads, dimensions, dimensions]` in FP32 semantic storage.
3666    fn gated_delta_scan(
3667        input: GatedDeltaScanInput<'_, Self::Tensor>,
3668        context: &<Self::Tensor as Tensor>::Context,
3669    ) -> Result<GatedDeltaScanOutput<Self::Tensor>, Error> {
3670        let _ = (input, context);
3671        Err(Error::backend(
3672            "gated-delta scan is not implemented by this backend",
3673        ))
3674    }
3675    /// Executes a grouped selective state-space recurrence in FP32 state.
3676    fn selective_state_space_scan(
3677        input: SelectiveStateSpaceScanInput<'_, Self::Tensor>,
3678        context: &<Self::Tensor as Tensor>::Context,
3679    ) -> Result<SelectiveStateSpaceScanOutput<Self::Tensor>, Error> {
3680        let _ = (input, context);
3681        Err(Error::backend(
3682            "selective state-space scan is not implemented by this backend",
3683        ))
3684    }
3685    /// Runs sparse attention over bounded local state and caller-selected
3686    /// compressed positions, optionally sharing softmax normalization with
3687    /// learned attention sinks.
3688    fn indexed_attention(
3689        input: IndexedAttentionInput<'_, Self::Tensor>,
3690        context: &<Self::Tensor as Tensor>::Context,
3691    ) -> Result<Self::Tensor, Error> {
3692        let _ = (input, context);
3693        Err(Error::backend(
3694            "indexed attention is not implemented by this backend",
3695        ))
3696    }
3697    /// Runs dense shared-softmax attention over local and pooled history.
3698    fn pooled_attention(
3699        input: PooledAttentionInput<'_, Self::Tensor>,
3700        context: &<Self::Tensor as Tensor>::Context,
3701    ) -> Result<Self::Tensor, Error> {
3702        let _ = (input, context);
3703        Err(Error::backend(
3704            "pooled attention is not implemented by this backend",
3705        ))
3706    }
3707    /// Selects pooled positions for indexed attention.
3708    fn select_pooled_positions(
3709        input: PooledPositionInput<'_, Self::Tensor>,
3710        context: &<Self::Tensor as Tensor>::Context,
3711    ) -> Result<Self::Tensor, Error> {
3712        let _ = (input, context);
3713        Err(Error::backend(
3714            "pooled-position selection is not implemented by this backend",
3715        ))
3716    }
3717    /// Gathers one full pooled-eligibility mask at selected positions.
3718    fn gather_pooled_mask(
3719        mask: &Self::Tensor,
3720        selected_positions: &Self::Tensor,
3721        context: &<Self::Tensor as Tensor>::Context,
3722    ) -> Result<Self::Tensor, Error> {
3723        let _ = (mask, selected_positions, context);
3724        Err(Error::backend(
3725            "pooled-mask gathering is not implemented by this backend",
3726        ))
3727    }
3728    /// Runs dense attention with optional learned per-head sink logits.
3729    fn attention_with_sinks(
3730        request: AttentionRequest<'_, Self::Tensor>,
3731        context: &<Self::Tensor as Tensor>::Context,
3732    ) -> Result<Self::Tensor, Error> {
3733        request.validate()?;
3734        if request.sinks.is_some() {
3735            return Err(Error::backend(
3736                "attention sinks are not implemented by this backend",
3737            ));
3738        }
3739        Self::attention(
3740            request.queries,
3741            request.keys,
3742            request.values,
3743            request.scale,
3744            request.mask,
3745            context,
3746        )
3747    }
3748    /// Runs causal sliding-window prefill attention with optional learned sinks.
3749    fn sliding_window_attention_with_sinks(
3750        request: AttentionRequest<'_, Self::Tensor>,
3751        window: i32,
3752        position_offset: i32,
3753        context: &<Self::Tensor as Tensor>::Context,
3754    ) -> Result<Self::Tensor, Error> {
3755        request.validate()?;
3756        if request.sinks.is_some() {
3757            return Err(Error::backend(
3758                "sliding-window attention sinks are not implemented by this backend",
3759            ));
3760        }
3761        Self::sliding_window_attention(
3762            request.queries,
3763            request.keys,
3764            request.values,
3765            request.scale,
3766            window,
3767            position_offset,
3768            context,
3769        )
3770    }
3771    /// Runs causal attention with caller-projected learned relative profiles.
3772    fn relative_attention(
3773        input: RelativeAttentionInput<'_, Self::Tensor>,
3774        context: &<Self::Tensor as Tensor>::Context,
3775    ) -> Result<Self::Tensor, Error> {
3776        let _ = (input, context);
3777        Err(Error::backend(
3778            "relative-profile attention is not implemented by this backend",
3779        ))
3780    }
3781    /// Applies RMS normalization without a learned scale.
3782    fn rms_norm_without_weight(
3783        input: &Self::Tensor,
3784        epsilon: f32,
3785        context: &<Self::Tensor as Tensor>::Context,
3786    ) -> Result<Self::Tensor, Error> {
3787        let _ = (input, epsilon, context);
3788        Err(Error::backend(
3789            "weightless RMS normalization is not implemented by this backend",
3790        ))
3791    }
3792    /// Applies RMS normalization with a caller-owned learned scale.
3793    ///
3794    /// The default keeps the operation portable by composing weightless
3795    /// normalization and multiplication. Backends may override it with a
3796    /// fused kernel when their tensor runtime provides one.
3797    fn rms_norm_with_weight(
3798        input: &Self::Tensor,
3799        weight: &Self::Tensor,
3800        epsilon: f32,
3801        context: &<Self::Tensor as Tensor>::Context,
3802    ) -> Result<Self::Tensor, Error> {
3803        Self::rms_norm_without_weight(input, epsilon, context)?.multiply(weight, context)
3804    }
3805    /// Applies a validated gated-product equation.
3806    fn gated_product(
3807        gate: Self::Tensor,
3808        up: Self::Tensor,
3809        policy: GatedProductPolicy,
3810        context: &<Self::Tensor as Tensor>::Context,
3811    ) -> Result<Self::Tensor, Error>;
3812    /// Runs un-cached scaled dot-product attention.
3813    fn attention(
3814        queries: Self::Tensor,
3815        keys: Self::Tensor,
3816        values: Self::Tensor,
3817        scale: f32,
3818        mask: Option<&Self::Tensor>,
3819        context: &<Self::Tensor as Tensor>::Context,
3820    ) -> Result<Self::Tensor, Error>;
3821    /// Runs causal sliding-window prefill attention without a square mask.
3822    #[allow(clippy::too_many_arguments)]
3823    fn sliding_window_attention(
3824        queries: Self::Tensor,
3825        keys: Self::Tensor,
3826        values: Self::Tensor,
3827        scale: f32,
3828        window: i32,
3829        position_offset: i32,
3830        context: &<Self::Tensor as Tensor>::Context,
3831    ) -> Result<Self::Tensor, Error>;
3832    /// Builds the backend-native boolean causal mask used for a prefill.
3833    ///
3834    /// The returned value remains a lazy/backend-owned tensor. Calling this
3835    /// method must not synchronize or materialize mask contents on the host.
3836    fn causal_mask(
3837        sequence: i32,
3838        offset: i32,
3839        window: Option<i32>,
3840        context: &<Self::Tensor as Tensor>::Context,
3841    ) -> Result<Self::Tensor, Error>;
3842    /// Applies a row-parallel projection and its collective reduction.
3843    fn row_parallel_linear(
3844        linear: &mut Self::Linear,
3845        input: &Self::Tensor,
3846        parallel: &Self::ParallelContext,
3847        context: &<Self::Tensor as Tensor>::Context,
3848    ) -> Result<Self::Tensor, Error>;
3849    /// Number of participants in a tensor-parallel collective context.
3850    fn parallel_size(_parallel: &Self::ParallelContext) -> usize {
3851        1
3852    }
3853}
3854
3855/// Additive mechanisms required only by distributed vocabulary and reduction paths.
3856///
3857/// Ordinary replicated backends implement [`NeuralBackend`] without this trait.
3858/// Every operation here is required, so an admitted distributed architecture cannot
3859/// encounter an inherited forward-time “unsupported” implementation.
3860pub trait DistributedNeuralBackend: NeuralBackend {
3861    /// Builds one rank-local vocabulary embedding under validated ownership.
3862    fn vocabulary_parallel_embedding(
3863        spec: EmbeddingSpec,
3864        range: VocabularyParallelRange,
3865        context: &<Self::Tensor as Tensor>::Context,
3866    ) -> Result<Self::Embedding, Error>;
3867    /// Builds one rank-local vocabulary output projection.
3868    fn vocabulary_parallel_linear(
3869        spec: LinearSpec,
3870        range: VocabularyParallelRange,
3871        context: &<Self::Tensor as Tensor>::Context,
3872    ) -> Result<Self::Linear, Error>;
3873    /// Looks up global token IDs and sums this rank's local contribution.
3874    fn vocabulary_parallel_lookup(
3875        embedding: &mut Self::Embedding,
3876        input: &Self::Tensor,
3877        policy: EmbeddingLookupPolicy,
3878        parallel: &Self::ParallelContext,
3879        context: &<Self::Tensor as Tensor>::Context,
3880    ) -> Result<Self::Tensor, Error>;
3881    /// Projects to local vocabulary rows and gathers complete logits.
3882    fn vocabulary_parallel_project(
3883        linear: &mut Self::Linear,
3884        input: &Self::Tensor,
3885        parallel: &Self::ParallelContext,
3886        context: &<Self::Tensor as Tensor>::Context,
3887    ) -> Result<Self::Tensor, Error>;
3888    /// Projects through a rank-local vocabulary embedding and gathers complete logits.
3889    fn vocabulary_parallel_embedding_project(
3890        embedding: &mut Self::Embedding,
3891        input: &Self::Tensor,
3892        parallel: &Self::ParallelContext,
3893        context: &<Self::Tensor as Tensor>::Context,
3894    ) -> Result<Self::Tensor, Error>;
3895    /// Sums a rank-local tensor contribution across the tensor-parallel group.
3896    fn sum_parallel(
3897        value: Self::Tensor,
3898        parallel: &Self::ParallelContext,
3899        context: &<Self::Tensor as Tensor>::Context,
3900    ) -> Result<Self::Tensor, Error>;
3901}
3902
3903/// Projected inputs to one gated-delta recurrent scan.
3904#[derive(Debug, Clone, Copy)]
3905pub struct GatedDeltaScanInput<'a, T> {
3906    /// Normalized queries `[batch, sequence, heads, dimensions]`.
3907    pub query: &'a T,
3908    /// Normalized keys `[batch, sequence, heads, dimensions]`.
3909    pub key: &'a T,
3910    /// Values `[batch, sequence, heads, dimensions]`.
3911    pub value: &'a T,
3912    /// Log transition decay, scalar- or vector-valued per head.
3913    pub log_decay: &'a T,
3914    /// Update strength `[batch, sequence, heads]`.
3915    pub beta: &'a T,
3916    /// Optional FP32 recurrent matrix.
3917    pub initial_state: Option<&'a T>,
3918}
3919
3920/// Final state and per-token output of a gated-delta scan.
3921#[derive(Debug, Clone)]
3922pub struct GatedDeltaScanOutput<T> {
3923    /// FP32 recurrent state after the final token.
3924    pub state: T,
3925    /// Recurrent output `[batch, sequence, heads, dimensions]`.
3926    pub output: T,
3927}
3928
3929/// Projected inputs to one Mamba-style selective state-space scan.
3930#[derive(Debug, Clone, Copy)]
3931pub struct SelectiveStateSpaceScanInput<'a, T> {
3932    /// Values `[batch, sequence, heads, head_dimensions]`.
3933    pub values: &'a T,
3934    /// Expanded input state vectors `[batch, sequence, heads, state_dimensions]`.
3935    pub input_state: &'a T,
3936    /// Expanded output state vectors `[batch, sequence, heads, state_dimensions]`.
3937    pub output_state: &'a T,
3938    /// Unnormalized timesteps `[batch, sequence, heads]`.
3939    pub time_step: &'a T,
3940    /// Per-head timestep bias `[heads]`.
3941    pub time_step_bias: &'a T,
3942    /// Per-head logarithmic transition magnitude `[heads]`.
3943    pub transition_log: &'a T,
3944    /// Per-head direct skip coefficient `[heads]`.
3945    pub skip: &'a T,
3946    /// Optional FP32 state `[batch, heads, head_dimensions, state_dimensions]`.
3947    pub initial_state: Option<&'a T>,
3948    /// Lower bound applied after softplus timestep discretization.
3949    pub time_step_floor: f32,
3950    /// Maximum number of prefill tokens processed per backend chunk.
3951    pub chunk_size: usize,
3952}
3953
3954/// Final state and per-token output of a selective state-space scan.
3955#[derive(Debug, Clone)]
3956pub struct SelectiveStateSpaceScanOutput<T> {
3957    /// FP32 recurrent state after the final token.
3958    pub state: T,
3959    /// Scan output `[batch, sequence, heads, head_dimensions]`.
3960    pub output: T,
3961}
3962
3963/// Deterministic host reference for the selective state-space recurrence.
3964#[allow(clippy::too_many_arguments)]
3965pub fn reference_selective_state_space_scan(
3966    batch: usize,
3967    sequence: usize,
3968    heads: usize,
3969    head_dimensions: usize,
3970    state_dimensions: usize,
3971    values: &[f32],
3972    input_state: &[f32],
3973    output_state: &[f32],
3974    time_step: &[f32],
3975    time_step_bias: &[f32],
3976    transition_log: &[f32],
3977    skip: &[f32],
3978    time_step_floor: f32,
3979    initial_state: Option<&[f32]>,
3980) -> Result<(Vec<f32>, Vec<f32>), Error> {
3981    let groups = batch * sequence * heads;
3982    let values_len = groups * head_dimensions;
3983    let vectors_len = groups * state_dimensions;
3984    let state_len = batch * heads * head_dimensions * state_dimensions;
3985    if values.len() != values_len
3986        || input_state.len() != vectors_len
3987        || output_state.len() != vectors_len
3988        || time_step.len() != groups
3989        || time_step_bias.len() != heads
3990        || transition_log.len() != heads
3991        || skip.len() != heads
3992        || initial_state.is_some_and(|state| state.len() != state_len)
3993        || !time_step_floor.is_finite()
3994        || time_step_floor < 0.0
3995    {
3996        return Err(Error::backend(
3997            "invalid selective state-space reference geometry",
3998        ));
3999    }
4000    let mut state = initial_state.map_or_else(|| vec![0.0; state_len], <[f32]>::to_vec);
4001    let mut output = vec![0.0; values_len];
4002    for batch_index in 0..batch {
4003        for token in 0..sequence {
4004            for head in 0..heads {
4005                let group = (batch_index * sequence + token) * heads + head;
4006                let dt =
4007                    ((time_step[group] + time_step_bias[head]).exp().ln_1p()).max(time_step_floor);
4008                let transition = (-transition_log[head].exp() * dt).exp();
4009                let vector_base = group * state_dimensions;
4010                for dimension in 0..head_dimensions {
4011                    let value_index = group * head_dimensions + dimension;
4012                    let state_base =
4013                        (batch_index * heads + head) * head_dimensions * state_dimensions
4014                            + dimension * state_dimensions;
4015                    let value = values[value_index];
4016                    let mut projected = 0.0f32;
4017                    for state_dimension in 0..state_dimensions {
4018                        let state_index = state_base + state_dimension;
4019                        state[state_index] = state[state_index] * transition
4020                            + dt * input_state[vector_base + state_dimension] * value;
4021                        projected +=
4022                            state[state_index] * output_state[vector_base + state_dimension];
4023                    }
4024                    output[value_index] = projected + value * skip[head];
4025                }
4026            }
4027        }
4028    }
4029    Ok((state, output))
4030}
4031
4032/// Deterministic host reference for the gated-delta recurrence.
4033///
4034/// Query, key, and value use flattened `[batch, sequence, heads, dimensions]`
4035/// storage. Decay is either `[batch, sequence, heads]` or the same vector
4036/// shape as query/key. Returned state is `[batch, heads, key_dim, value_dim]`.
4037#[allow(clippy::too_many_arguments)]
4038pub fn reference_gated_delta_scan(
4039    batch: usize,
4040    sequence: usize,
4041    heads: usize,
4042    key_dim: usize,
4043    value_dim: usize,
4044    query: &[f32],
4045    key: &[f32],
4046    value: &[f32],
4047    log_decay: &[f32],
4048    vector_decay: bool,
4049    beta: &[f32],
4050    initial_state: Option<&[f32]>,
4051) -> Result<(Vec<f32>, Vec<f32>), Error> {
4052    let key_values = batch * sequence * heads * key_dim;
4053    let values = batch * sequence * heads * value_dim;
4054    let groups = batch * sequence * heads;
4055    let state_values = batch * heads * key_dim * value_dim;
4056    if query.len() != key_values
4057        || key.len() != key_values
4058        || value.len() != values
4059        || beta.len() != groups
4060        || log_decay.len() != if vector_decay { key_values } else { groups }
4061        || initial_state.is_some_and(|state| state.len() != state_values)
4062    {
4063        return Err(Error::backend("invalid gated-delta reference geometry"));
4064    }
4065    let mut state = initial_state.map_or_else(|| vec![0.0; state_values], <[f32]>::to_vec);
4066    let mut output = vec![0.0; values];
4067    for batch_index in 0..batch {
4068        for token in 0..sequence {
4069            for head in 0..heads {
4070                let group = (batch_index * sequence + token) * heads + head;
4071                let state_group = (batch_index * heads + head) * key_dim * value_dim;
4072                for value_index in 0..value_dim {
4073                    let mut memory = 0.0f32;
4074                    for key_index in 0..key_dim {
4075                        let vector_index = group * key_dim + key_index;
4076                        let decay = if vector_decay {
4077                            log_decay[vector_index]
4078                        } else {
4079                            log_decay[group]
4080                        }
4081                        .exp();
4082                        let state_index = state_group + key_index * value_dim + value_index;
4083                        state[state_index] *= decay;
4084                        memory += state[state_index] * key[vector_index];
4085                    }
4086                    let value_index_flat = group * value_dim + value_index;
4087                    let delta = (value[value_index_flat] - memory) * beta[group];
4088                    let mut accumulated = 0.0f32;
4089                    for key_index in 0..key_dim {
4090                        let vector_index = group * key_dim + key_index;
4091                        let state_index = state_group + key_index * value_dim + value_index;
4092                        state[state_index] += key[vector_index] * delta;
4093                        accumulated += state[state_index] * query[vector_index];
4094                    }
4095                    output[value_index_flat] = accumulated;
4096                }
4097            }
4098        }
4099    }
4100    Ok((state, output))
4101}
4102
4103#[cfg(test)]
4104mod gated_delta_reference_tests {
4105    use super::reference_gated_delta_scan;
4106
4107    #[test]
4108    fn chunked_continuation_matches_one_scan() {
4109        let query = [0.5, -0.25, 0.1, 0.2, -0.4, 0.8];
4110        let key = [0.3, 0.4, -0.2, 0.7, 0.6, -0.1];
4111        let value = [1.0, -0.5, 0.25, 0.75, -0.3, 0.9];
4112        let decay = [-0.2, -0.1, -0.4, -0.3, -0.5, -0.25];
4113        let beta = [0.8, 0.6, 0.4];
4114        let (expected_state, expected) = reference_gated_delta_scan(
4115            1, 3, 1, 2, 2, &query, &key, &value, &decay, true, &beta, None,
4116        )
4117        .unwrap();
4118        let (state, mut actual) = reference_gated_delta_scan(
4119            1,
4120            2,
4121            1,
4122            2,
4123            2,
4124            &query[..4],
4125            &key[..4],
4126            &value[..4],
4127            &decay[..4],
4128            true,
4129            &beta[..2],
4130            None,
4131        )
4132        .unwrap();
4133        let (actual_state, tail) = reference_gated_delta_scan(
4134            1,
4135            1,
4136            1,
4137            2,
4138            2,
4139            &query[4..],
4140            &key[4..],
4141            &value[4..],
4142            &decay[4..],
4143            true,
4144            &beta[2..],
4145            Some(&state),
4146        )
4147        .unwrap();
4148        actual.extend(tail);
4149        assert!(expected
4150            .iter()
4151            .zip(actual)
4152            .all(|(left, right)| (left - right).abs() < 1e-6));
4153        assert!(expected_state
4154            .iter()
4155            .zip(actual_state)
4156            .all(|(left, right)| (left - right).abs() < 1e-6));
4157    }
4158}
4159
4160#[cfg(test)]
4161mod selective_state_space_reference_tests {
4162    use super::reference_selective_state_space_scan;
4163
4164    #[test]
4165    fn continuation_matches_one_scan() {
4166        let values = [0.2, -0.4, 0.8, 0.5, -0.3, 0.7];
4167        let input_state = [0.1, 0.3, -0.2, 0.4, 0.6, -0.5];
4168        let output_state = [0.7, -0.1, 0.2, 0.5, -0.4, 0.9];
4169        let time_step = [-0.3, 0.1, -0.2];
4170        let bias = [0.05];
4171        let transition = [-0.4];
4172        let skip = [0.25];
4173        let (expected_state, expected) = reference_selective_state_space_scan(
4174            1,
4175            3,
4176            1,
4177            2,
4178            2,
4179            &values,
4180            &input_state,
4181            &output_state,
4182            &time_step,
4183            &bias,
4184            &transition,
4185            &skip,
4186            0.001,
4187            None,
4188        )
4189        .unwrap();
4190        let (state, mut actual) = reference_selective_state_space_scan(
4191            1,
4192            2,
4193            1,
4194            2,
4195            2,
4196            &values[..4],
4197            &input_state[..4],
4198            &output_state[..4],
4199            &time_step[..2],
4200            &bias,
4201            &transition,
4202            &skip,
4203            0.001,
4204            None,
4205        )
4206        .unwrap();
4207        let (actual_state, tail) = reference_selective_state_space_scan(
4208            1,
4209            1,
4210            1,
4211            2,
4212            2,
4213            &values[4..],
4214            &input_state[4..],
4215            &output_state[4..],
4216            &time_step[2..],
4217            &bias,
4218            &transition,
4219            &skip,
4220            0.001,
4221            Some(&state),
4222        )
4223        .unwrap();
4224        actual.extend(tail);
4225        assert!(expected
4226            .iter()
4227            .zip(actual)
4228            .all(|(left, right)| (left - right).abs() < 1e-6));
4229        assert!(expected_state
4230            .iter()
4231            .zip(actual_state)
4232            .all(|(left, right)| (left - right).abs() < 1e-6));
4233    }
4234}
4235
4236/// Opaque tensor handle and the neural operations required by shared Eredu
4237/// architectures.
4238///
4239/// Implementations must preserve backend-native execution semantics. None of
4240/// these operations imply host materialization or synchronization.
4241pub trait Tensor: Clone + Debug + Sized + 'static {
4242    /// Backend execution context, such as a stream or command queue.
4243    type Context: ?Sized;
4244
4245    /// Logical tensor shape maintained without materializing tensor values.
4246    fn shape(&self) -> &[i32];
4247
4248    /// Returns one logical dimension.
4249    fn dim(&self, axis: usize) -> i32 {
4250        self.shape()[axis]
4251    }
4252
4253    /// Allocates an unloaded floating-point parameter tensor.
4254    fn unloaded_f32(shape: &[i32], context: &Self::Context) -> Result<Self, Error>;
4255    /// Allocates an unloaded signed 32-bit integer parameter tensor.
4256    fn unloaded_i32(shape: &[i32], context: &Self::Context) -> Result<Self, Error> {
4257        let _ = (shape, context);
4258        Err(Error::backend(
4259            "I32 parameter allocation is not implemented by this backend",
4260        ))
4261    }
4262    /// Creates a floating-point tensor from host initialization data.
4263    fn from_f32_slice(
4264        values: &[f32],
4265        shape: &[i32],
4266        context: &Self::Context,
4267    ) -> Result<Self, Error>;
4268    /// Creates a signed 32-bit integer tensor from host initialization data.
4269    fn from_i32_slice(
4270        values: &[i32],
4271        shape: &[i32],
4272        context: &Self::Context,
4273    ) -> Result<Self, Error> {
4274        let _ = (values, shape, context);
4275        Err(Error::backend(
4276            "I32 tensor construction is not implemented by this backend",
4277        ))
4278    }
4279    /// Explicitly materializes floating-point tensor values on the host.
4280    fn to_f32_vec(&self, context: &Self::Context) -> Result<Vec<f32>, Error> {
4281        let _ = context;
4282        Err(Error::backend(
4283            "F32 host materialization is not implemented by this backend",
4284        ))
4285    }
4286    /// Explicitly materializes signed 32-bit integer tensor values on the host.
4287    fn to_i32_vec(&self, context: &Self::Context) -> Result<Vec<i32>, Error> {
4288        let _ = context;
4289        Err(Error::backend(
4290            "I32 host materialization is not implemented by this backend",
4291        ))
4292    }
4293    /// Creates a floating-point tensor filled with one scalar.
4294    fn full_f32(value: f32, shape: &[i32], context: &Self::Context) -> Result<Self, Error> {
4295        let _ = (value, shape, context);
4296        Err(Error::backend(
4297            "filled tensor construction is not implemented by this backend",
4298        ))
4299    }
4300    /// Creates a signed 32-bit integer tensor filled with one scalar.
4301    fn full_i32(value: i32, shape: &[i32], context: &Self::Context) -> Result<Self, Error> {
4302        let _ = (value, shape, context);
4303        Err(Error::backend(
4304            "filled I32 tensor construction is not implemented by this backend",
4305        ))
4306    }
4307    /// Elementwise addition.
4308    fn add(&self, rhs: &Self, context: &Self::Context) -> Result<Self, Error>;
4309    /// Elementwise subtraction.
4310    fn subtract(&self, rhs: &Self, context: &Self::Context) -> Result<Self, Error>;
4311    /// Elementwise multiplication.
4312    fn multiply(&self, rhs: &Self, context: &Self::Context) -> Result<Self, Error>;
4313    /// Elementwise multiplication by a floating-point scalar.
4314    fn multiply_scalar(&self, rhs: f32, context: &Self::Context) -> Result<Self, Error>;
4315    /// Elementwise division.
4316    fn divide(&self, rhs: &Self, context: &Self::Context) -> Result<Self, Error>;
4317    /// Elementwise square.
4318    fn square(&self, context: &Self::Context) -> Result<Self, Error>;
4319    /// Elementwise hyperbolic tangent.
4320    fn tanh(&self, context: &Self::Context) -> Result<Self, Error> {
4321        let _ = context;
4322        Err(Error::backend(
4323            "tanh is not implemented by this tensor backend",
4324        ))
4325    }
4326    /// Elementwise maximum with a scalar.
4327    fn maximum_scalar(&self, rhs: f32, context: &Self::Context) -> Result<Self, Error>;
4328    /// Elementwise clamp using backend tensor bounds that may be scalar or broadcastable.
4329    fn clip(&self, minimum: &Self, maximum: &Self, context: &Self::Context) -> Result<Self, Error> {
4330        let _ = (minimum, maximum, context);
4331        Err(Error::backend(
4332            "clip is not implemented by this tensor backend",
4333        ))
4334    }
4335
4336    /// Softmax over one axis.
4337    fn softmax_axis(
4338        &self,
4339        axis: i32,
4340        precise: bool,
4341        context: &Self::Context,
4342    ) -> Result<Self, Error> {
4343        let _ = (axis, precise, context);
4344        Err(Error::backend(
4345            "softmax is not implemented by this tensor backend",
4346        ))
4347    }
4348
4349    /// Reshapes without changing logical element order.
4350    fn reshape(&self, shape: &[i32], context: &Self::Context) -> Result<Self, Error>;
4351    /// Broadcasts to a compatible target shape.
4352    fn broadcast_to(&self, shape: &[i32], context: &Self::Context) -> Result<Self, Error> {
4353        let _ = (shape, context);
4354        Err(Error::backend(
4355            "broadcasting is not implemented by this tensor backend",
4356        ))
4357    }
4358    /// Permutes axes.
4359    fn transpose_axes(&self, axes: &[i32], context: &Self::Context) -> Result<Self, Error>;
4360    /// Swaps two axes.
4361    fn swap_axes(&self, left: i32, right: i32, context: &Self::Context) -> Result<Self, Error>;
4362    /// Reverses the axes of a rank-two tensor.
4363    fn transpose(&self, context: &Self::Context) -> Result<Self, Error>;
4364    /// Inserts one unit dimension.
4365    fn expand_dims(&self, axis: i32, context: &Self::Context) -> Result<Self, Error>;
4366    /// Removes unit dimensions.
4367    fn squeeze_axes(&self, axes: &[i32], context: &Self::Context) -> Result<Self, Error>;
4368    /// Creates a tensor view using backend-neutral axis indexes.
4369    fn index(&self, indexes: &[Index], context: &Self::Context) -> Result<Self, Error>;
4370    /// Takes rows along one axis using a backend index tensor.
4371    fn take_axis(&self, indexes: &Self, axis: i32, context: &Self::Context) -> Result<Self, Error>;
4372    /// Creates a zero tensor with the same shape and physical dtype.
4373    fn zeros_like(&self, context: &Self::Context) -> Result<Self, Error> {
4374        let _ = context;
4375        Err(Error::backend(
4376            "dtype-preserving zero allocation is not implemented by this tensor backend",
4377        ))
4378    }
4379    /// Compares every element with one signed integer scalar.
4380    fn equal_i32(&self, value: i32, context: &Self::Context) -> Result<Self, Error> {
4381        let _ = (value, context);
4382        Err(Error::backend(
4383            "integer scalar comparison is not implemented by this tensor backend",
4384        ))
4385    }
4386    /// Elementwise logical disjunction over two boolean tensors.
4387    fn logical_or(&self, rhs: &Self, context: &Self::Context) -> Result<Self, Error> {
4388        let _ = (rhs, context);
4389        Err(Error::backend(
4390            "logical disjunction is not implemented by this tensor backend",
4391        ))
4392    }
4393    /// Selects elements from two broadcast-compatible tensors using a boolean condition.
4394    fn where_condition(
4395        condition: &Self,
4396        when_true: &Self,
4397        when_false: &Self,
4398        context: &Self::Context,
4399    ) -> Result<Self, Error> {
4400        let _ = (condition, when_true, when_false, context);
4401        Err(Error::backend(
4402            "conditional selection is not implemented by this tensor backend",
4403        ))
4404    }
4405    /// Scatters source rows into the true entries of a boolean mask.
4406    fn masked_scatter(
4407        &self,
4408        mask: &Self,
4409        source: &Self,
4410        context: &Self::Context,
4411    ) -> Result<Self, Error> {
4412        let _ = (mask, source, context);
4413        Err(Error::backend(
4414            "masked scatter is not implemented by this tensor backend",
4415        ))
4416    }
4417
4418    /// Applies rotary positions using caller-supplied reciprocal frequencies.
4419    fn rope_with_frequencies(
4420        &self,
4421        dimensions: i32,
4422        traditional: bool,
4423        offset: i32,
4424        frequencies: &Self,
4425        context: &Self::Context,
4426    ) -> Result<Self, Error> {
4427        let _ = (dimensions, traditional, offset, frequencies, context);
4428        Err(Error::backend(
4429            "explicit-frequency rotary positions are not implemented by this tensor backend",
4430        ))
4431    }
4432
4433    /// Concatenates tensors along an axis.
4434    fn concatenate(values: &[Self], axis: i32, context: &Self::Context) -> Result<Self, Error>;
4435    /// Stacks tensors along a new axis.
4436    fn stack(values: &[Self], axis: i32, context: &Self::Context) -> Result<Self, Error>;
4437    /// Matrix multiplication.
4438    fn matmul(lhs: &Self, rhs: &Self, context: &Self::Context) -> Result<Self, Error>;
4439    /// Reduction sum.
4440    fn sum_axis(
4441        value: &Self,
4442        axis: i32,
4443        keep_dims: bool,
4444        context: &Self::Context,
4445    ) -> Result<Self, Error>;
4446    /// Reduction mean.
4447    fn mean_axis(
4448        value: &Self,
4449        axis: i32,
4450        keep_dims: bool,
4451        context: &Self::Context,
4452    ) -> Result<Self, Error> {
4453        let width = value
4454            .shape()
4455            .get(if axis < 0 {
4456                usize::try_from(value.shape().len() as i32 + axis).unwrap_or(usize::MAX)
4457            } else {
4458                usize::try_from(axis).unwrap_or(usize::MAX)
4459            })
4460            .copied()
4461            .ok_or_else(|| Error::backend(format!("mean axis {axis} is out of range")))?;
4462        Self::sum_axis(value, axis, keep_dims, context)?
4463            .multiply_scalar(1.0 / width as f32, context)
4464    }
4465    /// Reduction argmin.
4466    fn argmin_axis(
4467        value: &Self,
4468        axis: i32,
4469        keep_dims: bool,
4470        context: &Self::Context,
4471    ) -> Result<Self, Error>;
4472    /// Pads a tensor.
4473    fn pad(
4474        value: &Self,
4475        widths: &[(i32, i32)],
4476        mode: PadMode,
4477        context: &Self::Context,
4478    ) -> Result<Self, Error>;
4479
4480    /// One-dimensional convolution.
4481    #[allow(clippy::too_many_arguments)]
4482    fn conv1d(
4483        input: &Self,
4484        weight: &Self,
4485        stride: i32,
4486        padding: i32,
4487        dilation: i32,
4488        groups: i32,
4489        context: &Self::Context,
4490    ) -> Result<Self, Error>;
4491    /// Two-dimensional convolution over canonical NHWC inputs and OHWI weights.
4492    #[allow(clippy::too_many_arguments)]
4493    fn conv2d(
4494        input: &Self,
4495        weight: &Self,
4496        stride: (i32, i32),
4497        padding: (i32, i32),
4498        dilation: (i32, i32),
4499        groups: i32,
4500        context: &Self::Context,
4501    ) -> Result<Self, Error> {
4502        let _ = (input, weight, stride, padding, dilation, groups, context);
4503        Err(Error::backend(
4504            "two-dimensional convolution is not implemented by this backend",
4505        ))
4506    }
4507    /// One-dimensional transposed convolution.
4508    #[allow(clippy::too_many_arguments)]
4509    fn conv_transpose1d(
4510        input: &Self,
4511        weight: &Self,
4512        stride: i32,
4513        padding: i32,
4514        dilation: i32,
4515        output_padding: i32,
4516        groups: i32,
4517        context: &Self::Context,
4518    ) -> Result<Self, Error>;
4519    /// Affine linear projection. Backends may use fused or quantized paths.
4520    fn linear(
4521        input: &Self,
4522        weight: &Self,
4523        bias: Option<&Self>,
4524        context: &Self::Context,
4525    ) -> Result<Self, Error>;
4526    /// Layer normalization.
4527    fn layer_norm(
4528        input: &Self,
4529        weight: Option<&Self>,
4530        bias: Option<&Self>,
4531        epsilon: f32,
4532        context: &Self::Context,
4533    ) -> Result<Self, Error>;
4534    /// Gaussian error linear unit.
4535    fn gelu(input: &Self, context: &Self::Context) -> Result<Self, Error>;
4536    /// Exponential linear unit.
4537    fn elu(input: &Self, alpha: f32, context: &Self::Context) -> Result<Self, Error>;
4538    /// Rotary positional encoding.
4539    #[allow(clippy::too_many_arguments)]
4540    fn rope(
4541        input: &Self,
4542        dimensions: i32,
4543        traditional: bool,
4544        base: f32,
4545        scale: f32,
4546        offset: i32,
4547        context: &Self::Context,
4548    ) -> Result<Self, Error>;
4549    /// Builds cosine and sine tensors for explicit multi-axis positions.
4550    fn multi_axis_rotary_embeddings(
4551        position_ids: &Self,
4552        spec: &multimodal::MultiAxisRotarySpec,
4553        context: &Self::Context,
4554    ) -> Result<(Self, Self), Error> {
4555        let _ = (position_ids, spec, context);
4556        Err(Error::backend(
4557            "multi-axis rotary embeddings are not implemented by this backend",
4558        ))
4559    }
4560    /// Projects selected output rows and scatters them into vocabulary order.
4561    fn masked_output_projection(
4562        input: multimodal::MaskedOutputProjectionInput<'_, Self>,
4563        context: &Self::Context,
4564    ) -> Result<Self, Error> {
4565        let _ = (input, context);
4566        Err(Error::backend(
4567            "masked output projection is not implemented by this backend",
4568        ))
4569    }
4570    /// Scaled dot-product attention. Backends retain control over fusion.
4571    fn scaled_dot_product_attention(
4572        queries: &Self,
4573        keys: &Self,
4574        values: &Self,
4575        scale: f32,
4576        mask: AttentionMask<'_, Self>,
4577        context: &Self::Context,
4578    ) -> Result<Self, Error>;
4579}
4580
4581/// One trainable parameter owned by a generic architecture module.
4582#[derive(Debug, Clone)]
4583pub struct Parameter<T> {
4584    spec: ParameterSpec,
4585    trainable: bool,
4586    value: T,
4587}
4588
4589impl<T> Parameter<T> {
4590    /// Wraps a backend tensor as an authoritative parameter slot.
4591    pub fn new(spec: ParameterSpec, value: T) -> Self {
4592        let trainable = spec.trainable;
4593        Self {
4594            spec,
4595            trainable,
4596            value,
4597        }
4598    }
4599    /// Borrows the backend tensor.
4600    pub const fn as_ref(&self) -> &T {
4601        &self.value
4602    }
4603    /// Replaces the backend tensor.
4604    pub fn replace(&mut self, value: T) {
4605        self.value = value;
4606    }
4607}
4608
4609impl<T: Tensor> Parameter<T> {
4610    /// Creates an unloaded floating-point parameter.
4611    pub fn unloaded(
4612        spec: ParameterSpec,
4613        shape: &[i32],
4614        context: &T::Context,
4615    ) -> Result<Self, Error> {
4616        Ok(Self::new(spec, T::unloaded_f32(shape, context)?))
4617    }
4618
4619    /// Creates an unloaded signed 32-bit integer parameter.
4620    pub fn unloaded_i32(
4621        spec: ParameterSpec,
4622        shape: &[i32],
4623        context: &T::Context,
4624    ) -> Result<Self, Error> {
4625        Ok(Self::new(spec, T::unloaded_i32(shape, context)?))
4626    }
4627}
4628
4629impl<T: 'static> Parameterized<T> for Parameter<T> {
4630    fn visit_parameters<'a, V>(&'a self, visitor: &mut V)
4631    where
4632        V: ParameterVisitor<'a, T>,
4633    {
4634        visitor.visit(
4635            ParameterMetadata::from_spec(&self.spec, self.trainable),
4636            &self.value,
4637        );
4638    }
4639
4640    fn visit_parameters_mut<'a, V>(&'a mut self, visitor: &mut V)
4641    where
4642        V: ParameterVisitorMut<'a, T>,
4643    {
4644        visitor.visit_mut(
4645            ParameterMetadata::from_spec(&self.spec, self.trainable),
4646            &mut self.value,
4647        );
4648    }
4649
4650    fn set_trainable(&mut self, trainable: bool) {
4651        self.trainable = trainable;
4652    }
4653}
4654
4655impl<T: 'static, M: Parameterized<T>> Parameterized<T> for Vec<M> {
4656    fn visit_parameters<'a, V>(&'a self, visitor: &mut V)
4657    where
4658        V: ParameterVisitor<'a, T>,
4659    {
4660        for module in self {
4661            module.visit_parameters(visitor);
4662        }
4663    }
4664
4665    fn visit_parameters_mut<'a, V>(&'a mut self, visitor: &mut V)
4666    where
4667        V: ParameterVisitorMut<'a, T>,
4668    {
4669        for module in self {
4670            module.visit_parameters_mut(visitor);
4671        }
4672    }
4673
4674    fn set_trainable(&mut self, trainable: bool) {
4675        for module in self {
4676            module.set_trainable(trainable);
4677        }
4678    }
4679}
4680
4681impl<T: 'static, M: Parameterized<T>> Parameterized<T> for Option<M> {
4682    fn visit_parameters<'a, V>(&'a self, visitor: &mut V)
4683    where
4684        V: ParameterVisitor<'a, T>,
4685    {
4686        if let Some(module) = self {
4687            module.visit_parameters(visitor);
4688        }
4689    }
4690
4691    fn visit_parameters_mut<'a, V>(&'a mut self, visitor: &mut V)
4692    where
4693        V: ParameterVisitorMut<'a, T>,
4694    {
4695        if let Some(module) = self {
4696            module.visit_parameters_mut(visitor);
4697        }
4698    }
4699
4700    fn set_trainable(&mut self, trainable: bool) {
4701        if let Some(module) = self {
4702            module.set_trainable(trainable);
4703        }
4704    }
4705}
4706
4707/// Post-convolution activation selected by architecture policy.
4708#[derive(Debug, Clone, Copy, Eq, PartialEq)]
4709pub enum ConvolutionActivation {
4710    /// Preserve the affine convolution output.
4711    Identity,
4712    /// Apply the sigmoid linear unit.
4713    Silu,
4714}
4715
4716/// Geometry and parameter identity for a causal depthwise convolution.
4717#[derive(Debug, Clone)]
4718pub struct CausalDepthwiseConvolutionSpec {
4719    /// Number of independent channels.
4720    pub channels: i32,
4721    /// Causal kernel width, including the current token.
4722    pub kernel_size: i32,
4723    /// Checkpoint-facing kernel stored as `[channels, 1, kernel]`.
4724    pub weight: ParameterSpec,
4725    /// Optional per-channel affine bias.
4726    pub bias: Option<ParameterSpec>,
4727    /// Activation applied after convolution and bias.
4728    pub activation: ConvolutionActivation,
4729}
4730
4731impl CausalDepthwiseConvolutionSpec {
4732    /// Validates positive geometry before allocating backend parameters.
4733    pub fn validate(&self) -> Result<(), Error> {
4734        if self.channels <= 0 {
4735            return Err(Error::backend(format!(
4736                "causal depthwise convolution channels must be positive, got {}",
4737                self.channels
4738            )));
4739        }
4740        if self.kernel_size <= 0 {
4741            return Err(Error::backend(format!(
4742                "causal depthwise convolution kernel size must be positive, got {}",
4743                self.kernel_size
4744            )));
4745        }
4746        Ok(())
4747    }
4748}
4749
4750/// Output and exact bounded history produced by one causal convolution call.
4751#[derive(Debug, Clone)]
4752pub struct CausalDepthwiseConvolutionOutput<T> {
4753    /// Activated convolution output shaped like the input.
4754    pub output: T,
4755    /// Last `kernel_size - 1` inputs, or `None` for a width-one kernel.
4756    pub history: Option<T>,
4757}
4758
4759/// Backend-neutral causal depthwise convolution.
4760///
4761/// The layer owns only parameters and equations. Callers keep the returned
4762/// bounded history in their runtime state realization.
4763#[derive(Debug, Clone, Parameterized)]
4764#[parameterized(tensor = "B::Tensor")]
4765pub struct CausalDepthwiseConvolution<B: NeuralBackend> {
4766    /// Checkpoint-layout kernel shaped `[channels, 1, kernel]`.
4767    pub weight: Parameter<B::Tensor>,
4768    /// Optional per-channel bias.
4769    pub bias: Option<Parameter<B::Tensor>>,
4770    #[parameter(skip)]
4771    channels: i32,
4772    #[parameter(skip)]
4773    kernel_size: i32,
4774    #[parameter(skip)]
4775    activation: ConvolutionActivation,
4776}
4777
4778impl<B: NeuralBackend> CausalDepthwiseConvolution<B> {
4779    /// Creates unloaded convolution parameters.
4780    pub fn new(
4781        spec: CausalDepthwiseConvolutionSpec,
4782        context: &<B::Tensor as Tensor>::Context,
4783    ) -> Result<Self, Error> {
4784        spec.validate()?;
4785        Ok(Self {
4786            weight: Parameter::unloaded(
4787                spec.weight,
4788                &[spec.channels, 1, spec.kernel_size],
4789                context,
4790            )?,
4791            bias: spec
4792                .bias
4793                .map(|bias| Parameter::unloaded(bias, &[spec.channels], context))
4794                .transpose()?,
4795            channels: spec.channels,
4796            kernel_size: spec.kernel_size,
4797            activation: spec.activation,
4798        })
4799    }
4800
4801    /// Returns the exact retained causal-history length.
4802    pub const fn history_len(&self) -> i32 {
4803        self.kernel_size - 1
4804    }
4805
4806    /// Applies the convolution and returns the replacement bounded history.
4807    pub fn forward(
4808        &self,
4809        input: &B::Tensor,
4810        history: Option<&B::Tensor>,
4811        context: &<B::Tensor as Tensor>::Context,
4812    ) -> Result<CausalDepthwiseConvolutionOutput<B::Tensor>, Error> {
4813        let shape = input.shape();
4814        if shape.len() != 3 || shape[0] <= 0 || shape[1] <= 0 || shape[2] != self.channels {
4815            return Err(Error::backend(format!(
4816                "causal depthwise convolution expects [batch, sequence, {}], got {shape:?}",
4817                self.channels
4818            )));
4819        }
4820        let history_len = self.history_len();
4821        let padded = if history_len == 0 {
4822            if history.is_some() {
4823                return Err(Error::backend(
4824                    "width-one causal convolution does not accept history",
4825                ));
4826            }
4827            input.clone()
4828        } else if let Some(history) = history {
4829            let expected = [shape[0], history_len, self.channels];
4830            if history.shape() != expected {
4831                return Err(Error::backend(format!(
4832                    "causal depthwise convolution history must have shape {expected:?}, got {:?}",
4833                    history.shape()
4834                )));
4835            }
4836            B::Tensor::concatenate(&[history.clone(), input.clone()], 1, context)?
4837        } else {
4838            B::Tensor::pad(
4839                input,
4840                &[(0, 0), (history_len, 0), (0, 0)],
4841                PadMode::Constant,
4842                context,
4843            )?
4844        };
4845        let execution_weight = self.weight.as_ref().swap_axes(1, 2, context)?;
4846        let mut output =
4847            B::Tensor::conv1d(&padded, &execution_weight, 1, 0, 1, self.channels, context)?;
4848        if output.shape() != shape {
4849            return Err(Error::backend(format!(
4850                "causal depthwise convolution backend returned shape {:?}, expected {shape:?}",
4851                output.shape()
4852            )));
4853        }
4854        if let Some(bias) = &self.bias {
4855            let bias = bias
4856                .as_ref()
4857                .reshape(&[1, 1, self.channels], context)?
4858                .broadcast_to(shape, context)?;
4859            output = output.add(&bias, context)?;
4860        }
4861        if self.activation == ConvolutionActivation::Silu {
4862            output = B::silu(output, context)?;
4863        }
4864        let history = (history_len > 0)
4865            .then(|| {
4866                padded.index(
4867                    &[
4868                        Index::Full,
4869                        Index::Range(shape[1], shape[1] + history_len),
4870                        Index::Full,
4871                    ],
4872                    context,
4873                )
4874            })
4875            .transpose()?;
4876        Ok(CausalDepthwiseConvolutionOutput { output, history })
4877    }
4878}
4879
4880/// Construction policy for a gated causal short convolution.
4881#[derive(Debug, Clone)]
4882pub struct GatedShortConvolutionSpec {
4883    /// Hidden width accepted by the fused input projection.
4884    pub input_dimensions: i32,
4885    /// Rank-local convolution channel count.
4886    pub channels: i32,
4887    /// Hidden width returned by the output projection.
4888    pub output_dimensions: i32,
4889    /// Fused B/C/x projection with output width `3 * channels`.
4890    pub input_projection: LinearSpec,
4891    /// Projection from convolution channels to the output width.
4892    pub output_projection: LinearSpec,
4893    /// Shared causal depthwise convolution parameters.
4894    pub convolution: CausalDepthwiseConvolutionSpec,
4895}
4896
4897impl GatedShortConvolutionSpec {
4898    /// Validates the fused segment and convolution geometry.
4899    pub fn validate(&self) -> Result<(), Error> {
4900        self.convolution.validate()?;
4901        let fused = self
4902            .channels
4903            .checked_mul(3)
4904            .ok_or_else(|| Error::backend("gated short-convolution width overflowed"))?;
4905        if self.input_dimensions <= 0
4906            || self.channels <= 0
4907            || self.output_dimensions <= 0
4908            || self.convolution.channels != self.channels
4909            || self.input_projection.input != self.input_dimensions
4910            || self.input_projection.output != fused
4911            || self.output_projection.input != self.channels
4912            || self.output_projection.output != self.output_dimensions
4913        {
4914            return Err(Error::backend(format!(
4915                "invalid gated short-convolution geometry input={} channels={} output={} fused_projection={}x{} output_projection={}x{} convolution_channels={}",
4916                self.input_dimensions,
4917                self.channels,
4918                self.output_dimensions,
4919                self.input_projection.input,
4920                self.input_projection.output,
4921                self.output_projection.input,
4922                self.output_projection.output,
4923                self.convolution.channels,
4924            )));
4925        }
4926        Ok(())
4927    }
4928}
4929
4930/// Output and replacement bounded state from a gated short convolution.
4931#[derive(Debug, Clone)]
4932pub struct GatedShortConvolutionOutput<T> {
4933    /// Projected hidden states.
4934    pub output: T,
4935    /// Last causal input values retained by the depthwise convolution.
4936    pub history: Option<T>,
4937}
4938
4939/// Fused gated short-convolution layer shared by hybrid decoders.
4940#[derive(Debug, Clone, Parameterized)]
4941#[parameterized(tensor = "B::Tensor")]
4942pub struct GatedShortConvolution<B: NeuralBackend> {
4943    /// Fused B/C/x input projection.
4944    pub input_projection: B::Linear,
4945    /// Shared causal depthwise convolution.
4946    pub convolution: CausalDepthwiseConvolution<B>,
4947    /// Output projection.
4948    pub output_projection: B::Linear,
4949    #[parameter(skip)]
4950    channels: i32,
4951}
4952
4953impl<B: NeuralBackend> GatedShortConvolution<B> {
4954    /// Builds one unloaded gated short convolution.
4955    pub fn new(
4956        spec: GatedShortConvolutionSpec,
4957        context: &<B::Tensor as Tensor>::Context,
4958    ) -> Result<Self, Error> {
4959        spec.validate()?;
4960        Ok(Self {
4961            input_projection: B::linear(spec.input_projection, context)?,
4962            convolution: CausalDepthwiseConvolution::new(spec.convolution, context)?,
4963            output_projection: B::linear(spec.output_projection, context)?,
4964            channels: spec.channels,
4965        })
4966    }
4967
4968    fn hidden(
4969        &mut self,
4970        input: &B::Tensor,
4971        history: Option<&B::Tensor>,
4972        context: &<B::Tensor as Tensor>::Context,
4973    ) -> Result<(B::Tensor, Option<B::Tensor>), Error> {
4974        let projected = self.input_projection.forward(input, context)?;
4975        let rank = projected.shape().len();
4976        if rank == 0 || projected.shape()[rank - 1] != 3 * self.channels {
4977            return Err(Error::backend(format!(
4978                "gated short-convolution projection returned shape {:?}, expected final width {}",
4979                projected.shape(),
4980                3 * self.channels
4981            )));
4982        }
4983        let mut segment = vec![Index::Full; rank];
4984        segment[rank - 1] = Index::Range(0, self.channels);
4985        let b = projected.index(&segment, context)?;
4986        segment[rank - 1] = Index::Range(self.channels, 2 * self.channels);
4987        let c = projected.index(&segment, context)?;
4988        segment[rank - 1] = Index::Range(2 * self.channels, 3 * self.channels);
4989        let x = projected.index(&segment, context)?;
4990        let convolution = self
4991            .convolution
4992            .forward(&b.multiply(&x, context)?, history, context)?;
4993        Ok((
4994            c.multiply(&convolution.output, context)?,
4995            convolution.history,
4996        ))
4997    }
4998
4999    /// Executes the replicated layer and returns replacement bounded state.
5000    pub fn forward(
5001        &mut self,
5002        input: &B::Tensor,
5003        history: Option<&B::Tensor>,
5004        context: &<B::Tensor as Tensor>::Context,
5005    ) -> Result<GatedShortConvolutionOutput<B::Tensor>, Error> {
5006        let (hidden, history) = self.hidden(input, history, context)?;
5007        Ok(GatedShortConvolutionOutput {
5008            output: self.output_projection.forward(&hidden, context)?,
5009            history,
5010        })
5011    }
5012
5013    /// Executes the same layer with a row-parallel output projection.
5014    pub fn forward_parallel(
5015        &mut self,
5016        input: &B::Tensor,
5017        history: Option<&B::Tensor>,
5018        parallel: &B::ParallelContext,
5019        context: &<B::Tensor as Tensor>::Context,
5020    ) -> Result<GatedShortConvolutionOutput<B::Tensor>, Error> {
5021        let (hidden, history) = self.hidden(input, history, context)?;
5022        Ok(GatedShortConvolutionOutput {
5023            output: B::row_parallel_linear(
5024                &mut self.output_projection,
5025                &hidden,
5026                parallel,
5027                context,
5028            )?,
5029            history,
5030        })
5031    }
5032}
5033
5034#[cfg(test)]
5035mod grouped_contract_tests {
5036    use super::*;
5037
5038    fn dense_format() -> LinearFormatSpec {
5039        LinearFormatSpec::unscaled(LinearFormat::Dense).unwrap()
5040    }
5041
5042    fn parameters(prefix: &str) -> GatedProductGroupParameters {
5043        let projection = |name| {
5044            GroupedProjectionSpec::new(
5045                ParameterSpec::trainable(name).unwrap(),
5046                None,
5047                dense_format(),
5048            )
5049            .unwrap()
5050        };
5051        GatedProductGroupParameters::new(
5052            projection(format!("{prefix}.gate.weight")),
5053            projection(format!("{prefix}.up.weight")),
5054            projection(format!("{prefix}.down.weight")),
5055        )
5056    }
5057
5058    #[test]
5059    fn top_k_selection_policy_rejects_invalid_counts() {
5060        assert!(TopKGroupSelectionSpec::new(8, 2, GroupScoring::Softmax, true).is_ok());
5061        assert!(TopKGroupSelectionSpec::new(0, 1, GroupScoring::Softmax, false).is_err());
5062        assert!(TopKGroupSelectionSpec::new(8, 9, GroupScoring::Softmax, false).is_err());
5063    }
5064
5065    #[test]
5066    fn gated_product_policy_rejects_malformed_scalars() {
5067        assert!(
5068            GatedProductPolicy::new(GatedProductActivation::Silu, Some(0.0), None, 1.0, 0.0,)
5069                .is_err()
5070        );
5071        assert!(GatedProductPolicy::new(
5072            GatedProductActivation::Silu,
5073            None,
5074            Some(f32::NAN),
5075            1.0,
5076            0.0,
5077        )
5078        .is_err());
5079        assert!(
5080            GatedProductPolicy::new(GatedProductActivation::Silu, None, None, 0.0, 0.0,).is_err()
5081        );
5082        assert!(GatedProductPolicy::new(
5083            GatedProductActivation::Silu,
5084            None,
5085            None,
5086            1.0,
5087            f32::INFINITY,
5088        )
5089        .is_err());
5090    }
5091
5092    #[test]
5093    fn selector_projection_and_correction_biases_require_distinct_identities() {
5094        let shared_bias = ParameterSpec::trainable("selector.bias").unwrap();
5095        let spec = TopKGroupSelectorSpec::new(
5096            4,
5097            ParameterSpec::trainable("selector.weight").unwrap(),
5098            dense_format(),
5099            TopKGroupSelectionSpec::new(2, 1, GroupScoring::SelectedSoftmax, false).unwrap(),
5100        )
5101        .unwrap()
5102        .with_bias(shared_bias.clone())
5103        .unwrap();
5104
5105        assert!(spec.with_correction_bias(shared_bias).is_err());
5106    }
5107
5108    #[test]
5109    fn independent_group_layout_requires_exact_cardinality() {
5110        assert!(GroupedGatedProductSpec::new(
5111            2,
5112            16,
5113            8,
5114            16,
5115            eredu_nn::GatedProductPolicy::ordinary_silu(),
5116            GatedProductGroupLayout::Independent(vec![parameters("e0"), parameters("e1")]),
5117        )
5118        .is_ok());
5119        assert!(GroupedGatedProductSpec::new(
5120            2,
5121            16,
5122            8,
5123            16,
5124            eredu_nn::GatedProductPolicy::ordinary_silu(),
5125            GatedProductGroupLayout::Independent(vec![parameters("e0")]),
5126        )
5127        .is_err());
5128    }
5129
5130    #[test]
5131    fn gated_product_bank_rejects_reused_projection_bias_identity() {
5132        let shared = ParameterSpec::trainable("groups.gate_up").unwrap();
5133        let gate_up = GroupedProjectionSpec::new(shared.clone(), Some(shared), dense_format());
5134        assert!(gate_up.is_err());
5135    }
5136
5137    #[test]
5138    fn quantized_group_projection_requires_explicit_companion_identities() {
5139        let format =
5140            LinearFormat::Affine(eredu_checkpoint::AffineQuantization::new(32, 4).unwrap());
5141        let projection = |format| {
5142            GroupedProjectionSpec::new(
5143                ParameterSpec::trainable("arbitrary.group.matrix").unwrap(),
5144                None,
5145                format,
5146            )
5147        };
5148        assert!(LinearFormatSpec::unscaled(format).is_err());
5149        assert!(projection(
5150            LinearFormatSpec::affine(
5151                format,
5152                ParameterSpec::trainable("unrelated.scale.identity").unwrap(),
5153                ParameterSpec::trainable("unrelated.affine.identity").unwrap(),
5154            )
5155            .unwrap()
5156        )
5157        .is_ok());
5158    }
5159}
5160
5161/// Generic affine projection.
5162#[derive(Debug, Clone)]
5163pub struct Linear<T> {
5164    /// Projection weights shaped `[output, input]`.
5165    pub weight: Parameter<T>,
5166    /// Optional output bias.
5167    pub bias: Option<Parameter<T>>,
5168}
5169
5170impl<T: Tensor> Linear<T> {
5171    /// Creates unloaded projection parameters.
5172    pub fn unloaded(spec: LinearSpec, context: &T::Context) -> Result<Self, Error> {
5173        Ok(Self {
5174            weight: Parameter::unloaded(spec.weight, &[spec.output, spec.input], context)?,
5175            bias: spec
5176                .bias
5177                .map(|bias| Parameter::unloaded(bias, &[spec.output], context))
5178                .transpose()?,
5179        })
5180    }
5181
5182    /// Applies the projection without materializing backend tensors.
5183    pub fn forward(&self, input: &T, context: &T::Context) -> Result<T, Error> {
5184        T::linear(
5185            input,
5186            self.weight.as_ref(),
5187            self.bias.as_ref().map(Parameter::as_ref),
5188            context,
5189        )
5190    }
5191}
5192
5193impl<T: 'static> Parameterized<T> for Linear<T> {
5194    fn visit_parameters<'a, V>(&'a self, visitor: &mut V)
5195    where
5196        V: ParameterVisitor<'a, T>,
5197    {
5198        self.weight.visit_parameters(visitor);
5199        if let Some(bias) = &self.bias {
5200            bias.visit_parameters(visitor);
5201        }
5202    }
5203
5204    fn visit_parameters_mut<'a, V>(&'a mut self, visitor: &mut V)
5205    where
5206        V: ParameterVisitorMut<'a, T>,
5207    {
5208        self.weight.visit_parameters_mut(visitor);
5209        if let Some(bias) = &mut self.bias {
5210            bias.visit_parameters_mut(visitor);
5211        }
5212    }
5213
5214    fn set_trainable(&mut self, trainable: bool) {
5215        self.weight.set_trainable(trainable);
5216        if let Some(bias) = &mut self.bias {
5217            bias.set_trainable(trainable);
5218        }
5219    }
5220}
5221
5222/// Generic affine layer normalization.
5223#[derive(Debug, Clone)]
5224pub struct LayerNorm<T> {
5225    /// Numerical stability epsilon.
5226    pub epsilon: f32,
5227    /// Optional trainable scale.
5228    pub weight: Option<Parameter<T>>,
5229    /// Optional trainable bias.
5230    pub bias: Option<Parameter<T>>,
5231}
5232
5233impl<T: Tensor> LayerNorm<T> {
5234    /// Creates an unloaded affine layer normalization.
5235    pub fn unloaded(
5236        dimensions: i32,
5237        epsilon: f32,
5238        weight: Option<ParameterSpec>,
5239        bias: Option<ParameterSpec>,
5240        context: &T::Context,
5241    ) -> Result<Self, Error> {
5242        Ok(Self {
5243            epsilon,
5244            weight: weight
5245                .map(|weight| Parameter::unloaded(weight, &[dimensions], context))
5246                .transpose()?,
5247            bias: bias
5248                .map(|bias| Parameter::unloaded(bias, &[dimensions], context))
5249                .transpose()?,
5250        })
5251    }
5252
5253    /// Applies layer normalization through the selected backend.
5254    pub fn forward(&self, input: &T, context: &T::Context) -> Result<T, Error> {
5255        T::layer_norm(
5256            input,
5257            self.weight.as_ref().map(Parameter::as_ref),
5258            self.bias.as_ref().map(Parameter::as_ref),
5259            self.epsilon,
5260            context,
5261        )
5262    }
5263}
5264
5265impl<T: 'static> Parameterized<T> for LayerNorm<T> {
5266    fn visit_parameters<'a, V>(&'a self, visitor: &mut V)
5267    where
5268        V: ParameterVisitor<'a, T>,
5269    {
5270        if let Some(weight) = &self.weight {
5271            weight.visit_parameters(visitor);
5272        }
5273        if let Some(bias) = &self.bias {
5274            bias.visit_parameters(visitor);
5275        }
5276    }
5277
5278    fn visit_parameters_mut<'a, V>(&'a mut self, visitor: &mut V)
5279    where
5280        V: ParameterVisitorMut<'a, T>,
5281    {
5282        if let Some(weight) = &mut self.weight {
5283            weight.visit_parameters_mut(visitor);
5284        }
5285        if let Some(bias) = &mut self.bias {
5286            bias.visit_parameters_mut(visitor);
5287        }
5288    }
5289
5290    fn set_trainable(&mut self, trainable: bool) {
5291        if let Some(weight) = &mut self.weight {
5292            weight.set_trainable(trainable);
5293        }
5294        if let Some(bias) = &mut self.bias {
5295            bias.set_trainable(trainable);
5296        }
5297    }
5298}
5299
5300#[cfg(test)]
5301mod parameter_topology_tests {
5302    use super::*;
5303
5304    #[derive(Parameterized)]
5305    #[parameterized(tensor = "i32")]
5306    struct DerivedModule {
5307        first: Parameter<i32>,
5308        second: Option<Parameter<i32>>,
5309        #[parameter(skip)]
5310        label: &'static str,
5311    }
5312
5313    #[derive(Parameterized)]
5314    #[parameterized(tensor = "i32")]
5315    enum DerivedChoice {
5316        Present(Parameter<i32>),
5317        Empty,
5318    }
5319
5320    fn parameter(id: &str, value: i32) -> Parameter<i32> {
5321        Parameter::new(ParameterSpec::trainable(id).unwrap(), value)
5322    }
5323
5324    #[test]
5325    fn derive_recurses_through_structs_options_and_enums() {
5326        let mut module = DerivedModule {
5327            first: parameter("first.weight", 1),
5328            second: Some(parameter("second.weight", 2)),
5329            label: "not a parameter",
5330        };
5331        assert_eq!(module.label, "not a parameter");
5332        let metadata = validate_parameter_topology::<i32, _>(&module).unwrap();
5333        assert_eq!(
5334            metadata
5335                .iter()
5336                .map(|entry| entry.id.as_str())
5337                .collect::<Vec<_>>(),
5338            ["first.weight", "second.weight"]
5339        );
5340
5341        module.set_trainable(false);
5342        assert!(validate_parameter_topology::<i32, _>(&module)
5343            .unwrap()
5344            .iter()
5345            .all(|entry| !entry.trainable));
5346
5347        let choice = DerivedChoice::Present(parameter("choice.weight", 3));
5348        assert_eq!(
5349            validate_parameter_topology::<i32, _>(&choice).unwrap()[0]
5350                .id
5351                .as_str(),
5352            "choice.weight"
5353        );
5354        assert!(validate_parameter_topology::<i32, _>(&DerivedChoice::Empty)
5355            .unwrap()
5356            .is_empty());
5357    }
5358
5359    #[test]
5360    fn validation_rejects_duplicates_and_invalid_aliases() {
5361        let duplicate = vec![parameter("same.weight", 1), parameter("same.weight", 2)];
5362        assert!(matches!(
5363            validate_parameter_topology::<i32, _>(&duplicate),
5364            Err(ParameterTopologyError::DuplicateId(id)) if id.as_str() == "same.weight"
5365        ));
5366
5367        let alias = Parameter::new(
5368            ParameterSpec {
5369                id: ParameterId::new("alias.weight").unwrap(),
5370                trainable: true,
5371                alias_of: Some(ParameterId::new("missing.weight").unwrap()),
5372                group: None,
5373                linear_companion: None,
5374                linear_companion_of: None,
5375            },
5376            1,
5377        );
5378        assert!(matches!(
5379            validate_parameter_topology::<i32, _>(&alias),
5380            Err(ParameterTopologyError::MissingAliasDestination { .. })
5381        ));
5382    }
5383}
5384
5385#[cfg(test)]
5386mod fused_projection_layout_tests {
5387    use super::*;
5388
5389    #[test]
5390    fn component_major_layout_is_checked_and_stable() {
5391        let layout = FusedProjectionLayout::new([
5392            FusedProjectionSegment::new("query", 8).unwrap(),
5393            FusedProjectionSegment::new("key", 4).unwrap(),
5394            FusedProjectionSegment::new("value", 4).unwrap(),
5395        ])
5396        .unwrap();
5397        assert_eq!(layout.output_width(), 16);
5398        assert_eq!(
5399            layout
5400                .segments()
5401                .iter()
5402                .map(|segment| (segment.name(), segment.width()))
5403                .collect::<Vec<_>>(),
5404            [("query", 8), ("key", 4), ("value", 4)]
5405        );
5406        assert!(FusedProjectionLayout::new(Vec::new()).is_err());
5407        assert!(FusedProjectionLayout::new([
5408            FusedProjectionSegment::new("same", 1).unwrap(),
5409            FusedProjectionSegment::new("same", 1).unwrap(),
5410        ])
5411        .is_err());
5412        assert!(FusedProjectionSegment::new("", 1).is_err());
5413        assert!(FusedProjectionSegment::new("bad", 0).is_err());
5414    }
5415
5416    #[test]
5417    fn zero_sentinel_cannot_alias_an_embedding_row() {
5418        EmbeddingLookupPolicy::Strict.validate().unwrap();
5419        EmbeddingLookupPolicy::ZeroSentinel(-1).validate().unwrap();
5420        assert!(EmbeddingLookupPolicy::ZeroSentinel(0).validate().is_err());
5421    }
5422
5423    #[test]
5424    fn vocabulary_parallel_ownership_requires_exact_global_rows() {
5425        let range = VocabularyParallelRange {
5426            global_vocabulary: 5,
5427            local: 0..3,
5428        };
5429        range.validate_global_rows(5).unwrap();
5430        assert!(range.validate_global_rows(4).is_err());
5431        assert!(range.validate_global_rows(-1).is_err());
5432    }
5433}
5434
5435/// Generic rotary positional encoding configuration.
5436#[derive(Debug, Clone, Copy)]
5437pub struct Rope {
5438    dimensions: i32,
5439    traditional: bool,
5440    base: f32,
5441    scale: f32,
5442}
5443
5444impl Rope {
5445    /// Creates a rotary positional encoding.
5446    pub const fn new(dimensions: i32, traditional: bool, base: f32, scale: f32) -> Self {
5447        Self {
5448            dimensions,
5449            traditional,
5450            base,
5451            scale,
5452        }
5453    }
5454
5455    /// Applies rotary positional encoding through the selected backend.
5456    pub fn forward<T: Tensor>(
5457        &self,
5458        input: &T,
5459        offset: i32,
5460        context: &T::Context,
5461    ) -> Result<T, Error> {
5462        T::rope(
5463            input,
5464            self.dimensions,
5465            self.traditional,
5466            self.base,
5467            self.scale,
5468            offset,
5469            context,
5470        )
5471    }
5472}