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